[fiat] 01/01: New upstream version 2016.2.0

Johannes Ring johannr-guest at moszumanska.debian.org
Thu Dec 1 11:12:36 UTC 2016


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

johannr-guest pushed a commit to annotated tag upstream/2016.2.0
in repository fiat.

commit 4ee53648569532db362008063df806f10bb09229
Author: Johannes Ring <johannr at simula.no>
Date:   Thu Dec 1 11:56:50 2016 +0100

    New upstream version 2016.2.0
---
 .bzrignore                                      |   9 -
 .gitignore                                      |   5 -
 AUTHORS                                         |  42 ++
 ChangeLog                                       |  22 +
 FIAT/P0.py                                      |  51 +-
 FIAT/__init__.py                                |  63 +-
 FIAT/argyris.py                                 | 126 ++--
 FIAT/asci2vtk2d.py                              |  73 --
 FIAT/asci2vtk3d.py                              |  73 --
 FIAT/brezzi_douglas_fortin_marini.py            | 123 ++--
 FIAT/brezzi_douglas_marini.py                   |  83 +--
 FIAT/bubble.py                                  |  37 +
 FIAT/crouzeix_raviart.py                        |  34 +-
 FIAT/discontinuous.py                           |  95 +++
 FIAT/discontinuous_lagrange.py                  |  63 +-
 FIAT/discontinuous_raviart_thomas.py            |  63 +-
 FIAT/discontinuous_taylor.py                    |  69 ++
 FIAT/dual_set.py                                |  46 +-
 FIAT/enriched.py                                | 174 +++++
 FIAT/expansions.py                              | 164 ++---
 FIAT/finite_element.py                          | 261 +++++--
 FIAT/functional.py                              | 263 +++----
 FIAT/gauss_legendre.py                          |  44 ++
 FIAT/gauss_lobatto_legendre.py                  |  45 ++
 FIAT/hdiv_trace.py                              | 285 +++++++
 FIAT/hdivcurl.py                                | 267 +++++++
 FIAT/hellan_herrmann_johnson.py                 | 110 +++
 FIAT/hermite.py                                 |  62 +-
 FIAT/jacobi.py                                  |  73 +-
 FIAT/lagrange.py                                |  59 +-
 FIAT/makelags.py                                |  60 --
 FIAT/morley.py                                  |  49 +-
 FIAT/nedelec.py                                 | 314 ++++----
 FIAT/nedelec_second_kind.py                     |  71 +-
 FIAT/newdubiner.py                              | 254 -------
 FIAT/nodal_enriched.py                          | 143 ++++
 FIAT/orthopoly.py                               | 384 ++++++++++
 FIAT/polynomial_set.py                          | 146 ++--
 FIAT/quadrature.py                              | 310 ++++----
 FIAT/quadrature_schemes.py                      | 307 ++++++++
 FIAT/raviart_thomas.py                          | 180 +++--
 FIAT/reference_element.py                       | 942 +++++++++++++++++-------
 FIAT/regge.py                                   | 253 ++-----
 FIAT/restricted.py                              | 126 ++++
 FIAT/tabarg.py                                  |  51 --
 FIAT/tablag.py                                  |  33 -
 FIAT/tensor_product.py                          | 368 +++++++++
 FIAT/trace.py                                   | 269 -------
 FIAT/transform_hermite.py                       |  71 --
 FIAT/transform_morley.py                        | 103 ---
 MANIFEST                                        |  32 -
 README => README.rst                            |  46 +-
 doc/fenicsmanual.cls                            | 100 ---
 doc/sphinx/Makefile                             | 177 +++++
 doc/sphinx/requirements.txt                     |   2 +
 doc/sphinx/source/conf.py                       | 292 ++++++++
 doc/sphinx/source/index.rst                     |  37 +
 doc/sphinx/source/installation.rst              |  49 ++
 doc/{manual.tex => sphinx/source/manual.rst}    |  80 +-
 doc/sphinx/source/releases.rst                  |  13 +
 doc/sphinx/source/releases/next.rst             |  47 ++
 doc/sphinx/source/releases/v1.6.0.rst           |   8 +
 doc/sphinx/source/releases/v2016.1.0.rst        |   7 +
 release.conf                                    |   5 -
 setup.cfg                                       |   7 +
 setup.py                                        |   6 +-
 test/README                                     |   6 +
 FIAT/factorial.py => test/conftest.py           |  16 +-
 test/regression/.gitignore                      |   1 -
 test/regression/README.rst                      |  18 +-
 test/regression/conftest.py                     |  47 ++
 test/regression/fiat-reference-data-id          |   2 +-
 test/regression/scripts/getreferencerepo        |   4 +-
 test/regression/scripts/parameters              |   2 +-
 test/regression/{test.py => test_regression.py} | 224 ++----
 test/test.py                                    |  48 --
 test/unit/test.py                               |  66 --
 test/unit/test_discontinuous_taylor.py          |  51 ++
 test/unit/test_facet_support_dofs.py            | 164 +++++
 test/unit/test_fiat.py                          | 308 ++++++++
 test/unit/test_gauss_legendre.py                |  49 ++
 test/unit/test_gauss_lobatto_legendre.py        |  49 ++
 test/unit/test_hdivtrace.py                     | 111 +++
 test/unit/test_quadrature.py                    | 195 +++++
 test/unit/test_reference_element.py             | 101 +++
 test/unit/test_regge_hhj.py                     |  24 +
 test/unit/test_tensor_product.py                | 527 +++++++++++++
 87 files changed, 6938 insertions(+), 3301 deletions(-)

diff --git a/.bzrignore b/.bzrignore
deleted file mode 100644
index 4da66d2..0000000
--- a/.bzrignore
+++ /dev/null
@@ -1,9 +0,0 @@
-(^|/)CVS($|/)
-(^|/)\.hg($|/)
-(^|/)\.hgtags($|/)
-^project.log$
-^tailor.state$
-^tailor.state.old$
-^tailor.state.journal$
-syntax: glob
-FIAT/*.pyc
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 613b484..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-*.pyc
-
-build/
-dist/
-FIAT.egg-info/
diff --git a/AUTHORS b/AUTHORS
index 71c6ddc..5034723 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -23,5 +23,47 @@ Contributors:
     Andy R. Terrel
     email: aterrel at uchicago.edu
 
+    Andrew T. T. McRae
+    email: a.t.t.mcrae at bath.ac.uk
+
     Jan Blechta
     email: blechta at karlin.mff.cuni.cz
+
+    David A. Ham
+    email: david.ham at imperial.ac.uk
+
+    Miklós Homolya
+    email: m.homolya14 at imperial.ac.uk
+
+    Lawrence Mitchell
+    email: lawrence.mitchell at imperial.ac.uk
+
+    Colin Cotter
+    email: colin.cotter at imperial.ac.uk>
+
+    Thomas H. Gibson
+    email: t.gibson15 at imperial.ac.uk
+
+    Florian Rathgeber
+    email: florian.rathgeber at gmail.com
+
+    Aslak Bergersen
+    email: aslak.bergersen at gmail.com
+
+    Nico Schlömer
+    email: nico.schloemer at gmail.com
+
+    Johannes Ring
+    email: johannr at simula.no
+
+    Matt Knepley
+    email: knepley at rice.edu
+
+    Lizao Li
+    email: lzlarryli at gmail.com
+
+    Martin Sandve Alnæs
+    email: martinal at simula.no
+
+    Matthias Liertzer
+    email: matthias at liertzer.at
diff --git a/ChangeLog b/ChangeLog
index c19e185..5397a14 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2016.2.0 [2016-11-30]
+ - Enable Travis CI on GitHub
+ - Add Firedrake quadrilateral cell
+ - Add tensor product cell
+ - Add facet -> cell coordinate transformation
+ - Add Bubble element
+ - Add discontinuous Taylor element
+ - Add broken element and H(div) trace element
+ - Add element restrictions onto mesh entities
+ - Add tensor product elements (for tensor product cells)
+ - Add H(div) and H(curl) element-modifiers for TPEs
+ - Add enriched element, i.e. sum of elements (e.g. for building Mini)
+ - Add multidimensional taylor elements
+ - Add Gauss Lobatto Legendre elements
+ - Finding non-vanishing DoFs on a facets
+ - Add tensor product quadrature rule
+ - Make regression tests working again after few years
+ - Prune modules having only __main__ code including transform_morley,
+	transform_hermite (ff86250820e2b18f7a0df471c97afa87207e9a7d)
+ - Remove newdubiner module (b3b120d40748961fdd0727a4e6c62450198d9647,
+	reference removed by cb65a84ac639977b7be04962cc1351481ca66124)
+ - Switch from homebrew factorial/gamma to math module (wraps C std lib)
 2016.1.0 [2016-06-23]
  - Minor fixes
 1.6.0 [2015-07-28]
diff --git a/FIAT/P0.py b/FIAT/P0.py
index 286deaf..9bf4828 100644
--- a/FIAT/P0.py
+++ b/FIAT/P0.py
@@ -16,47 +16,42 @@
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 #
 # Written by Robert C. Kirby
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This work is partially supported by the US Department of Energy
 # under award number DE-FG02-04ER25650
-#
-# Last changed: 2005-05-16
 
-from . import reference_element, dual_set, functional, polynomial_set, finite_element
+from __future__ import absolute_import, print_function, division
+
+from FIAT import dual_set, functional, polynomial_set, finite_element
 import numpy
 
-class P0Dual( dual_set.DualSet ):
-    def __init__( self, ref_el ):
+
+class P0Dual(dual_set.DualSet):
+    def __init__(self, ref_el):
         entity_ids = {}
         nodes = []
-        vs = numpy.array( ref_el.get_vertices() )
-        bary=tuple( numpy.average( vs, 0 ) )
-        
-        nodes = [ functional.PointEvaluation( ref_el, bary ) ]
-        entity_ids = { }
+        vs = numpy.array(ref_el.get_vertices())
+        bary = tuple(numpy.average(vs, 0))
+
+        nodes = [functional.PointEvaluation(ref_el, bary)]
+        entity_ids = {}
         sd = ref_el.get_spatial_dimension()
         top = ref_el.get_topology()
-        for dim in sorted( top ):
+        for dim in sorted(top):
             entity_ids[dim] = {}
-            for entity in sorted( top[dim] ):
+            for entity in sorted(top[dim]):
                 entity_ids[dim][entity] = []
 
-        entity_ids[sd] = { 0 : [ 0 ] }
-        
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
-
-class P0( finite_element.FiniteElement ):
-    def __init__( self, ref_el ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, 0 )
-        dual = P0Dual( ref_el )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, 0 )
-
-if __name__ == "__main__":
-    T = reference_element.UFCTriangle()
-    U = P0( T )
-
-    print(U.get_dual_set().entity_ids)
-    print(U.get_nodal_basis().tabulate( T.make_lattice(1) ))
+        entity_ids[sd] = {0: [0]}
 
+        super(P0Dual, self).__init__(nodes, ref_el, entity_ids)
 
 
+class P0(finite_element.CiarletElement):
+    def __init__(self, ref_el):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, 0)
+        dual = P0Dual(ref_el)
+        degree = 0
+        formdegree = ref_el.get_spatial_dimension()  # n-form
+        super(P0, self).__init__(poly_set, dual, degree, formdegree)
diff --git a/FIAT/__init__.py b/FIAT/__init__.py
index 77fc657..b21ce41 100644
--- a/FIAT/__init__.py
+++ b/FIAT/__init__.py
@@ -2,19 +2,21 @@
 evaluating arbitrary order Lagrange and many other elements.
 Simplices in one, two, and three dimensions are supported."""
 
-__version__ = "2016.1.0"
+from __future__ import absolute_import, print_function, division
 
 # Import finite element classes
-from FIAT.finite_element import FiniteElement
+from FIAT.finite_element import FiniteElement, CiarletElement  # noqa: F401
 from FIAT.argyris import Argyris
 from FIAT.argyris import QuinticArgyris
 from FIAT.brezzi_douglas_marini import BrezziDouglasMarini
 from FIAT.brezzi_douglas_fortin_marini import BrezziDouglasFortinMarini
 from FIAT.discontinuous_lagrange import DiscontinuousLagrange
-from FIAT.trace import DiscontinuousLagrangeTrace
+from FIAT.discontinuous_taylor import DiscontinuousTaylor
 from FIAT.discontinuous_raviart_thomas import DiscontinuousRaviartThomas
 from FIAT.hermite import CubicHermite
 from FIAT.lagrange import Lagrange
+from FIAT.gauss_lobatto_legendre import GaussLobattoLegendre
+from FIAT.gauss_legendre import GaussLegendre
 from FIAT.morley import Morley
 from FIAT.nedelec import Nedelec
 from FIAT.nedelec_second_kind import NedelecSecondKind
@@ -22,27 +24,48 @@ from FIAT.P0 import P0
 from FIAT.raviart_thomas import RaviartThomas
 from FIAT.crouzeix_raviart import CrouzeixRaviart
 from FIAT.regge import Regge
+from FIAT.hellan_herrmann_johnson import HellanHerrmannJohnson
+from FIAT.bubble import Bubble
+from FIAT.tensor_product import TensorProductElement
+from FIAT.enriched import EnrichedElement
+from FIAT.nodal_enriched import NodalEnrichedElement
+from FIAT.discontinuous import DiscontinuousElement
+from FIAT.hdiv_trace import HDivTrace
+from FIAT.restricted import RestrictedElement             # noqa: F401
+
+# Important functionality
+from FIAT.quadrature import make_quadrature               # noqa: F401
+from FIAT.quadrature_schemes import create_quadrature     # noqa: F401
+from FIAT.reference_element import ufc_cell, ufc_simplex  # noqa: F401
+from FIAT.hdivcurl import Hdiv, Hcurl                     # noqa: F401
+
+__version__ = "2016.2.0"
 
 # List of supported elements and mapping to element classes
-supported_elements = {"Argyris":                      Argyris,
-                      "Brezzi-Douglas-Marini":        BrezziDouglasMarini,
+supported_elements = {"Argyris": Argyris,
+                      "Brezzi-Douglas-Marini": BrezziDouglasMarini,
                       "Brezzi-Douglas-Fortin-Marini": BrezziDouglasFortinMarini,
-                      "Crouzeix-Raviart":             CrouzeixRaviart,
-                      "Discontinuous Lagrange":       DiscontinuousLagrange,
-                      "Discontinuous Lagrange Trace": DiscontinuousLagrangeTrace,
+                      "Bubble": Bubble,
+                      "Crouzeix-Raviart": CrouzeixRaviart,
+                      "Discontinuous Lagrange": DiscontinuousLagrange,
+                      "Discontinuous Taylor": DiscontinuousTaylor,
                       "Discontinuous Raviart-Thomas": DiscontinuousRaviartThomas,
-                      "Hermite":                      CubicHermite,
-                      "Lagrange":                     Lagrange,
-                      "Morley":                       Morley,
-                      "Nedelec 1st kind H(curl)":     Nedelec,
-                      "Nedelec 2nd kind H(curl)":     NedelecSecondKind,
-                      "Raviart-Thomas":               RaviartThomas,
-                      "Regge":                        Regge}
+                      "Hermite": CubicHermite,
+                      "Lagrange": Lagrange,
+                      "Gauss-Lobatto-Legendre": GaussLobattoLegendre,
+                      "Gauss-Legendre": GaussLegendre,
+                      "Morley": Morley,
+                      "Nedelec 1st kind H(curl)": Nedelec,
+                      "Nedelec 2nd kind H(curl)": NedelecSecondKind,
+                      "Raviart-Thomas": RaviartThomas,
+                      "Regge": Regge,
+                      "EnrichedElement": EnrichedElement,
+                      "NodalEnrichedElement": NodalEnrichedElement,
+                      "TensorProductElement": TensorProductElement,
+                      "BrokenElement": DiscontinuousElement,
+                      "HDiv Trace": HDivTrace,
+                      "Hellan-Herrmann-Johnson": HellanHerrmannJohnson}
 
 # List of extra elements
-extra_elements = {"P0":              P0,
+extra_elements = {"P0": P0,
                   "Quintic Argyris": QuinticArgyris}
-
-# Important functionality
-from .quadrature import make_quadrature
-from .reference_element import ufc_simplex
diff --git a/FIAT/argyris.py b/FIAT/argyris.py
index e16dc46..9aa2c28 100644
--- a/FIAT/argyris.py
+++ b/FIAT/argyris.py
@@ -15,11 +15,13 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, polynomial_set, dual_set, functional 
-import numpy
+from __future__ import absolute_import, print_function, division
 
-class ArgyrisDualSet( dual_set.DualSet ):
-    def __init__( self, ref_el, degree ):
+from FIAT import finite_element, polynomial_set, dual_set, functional
+
+
+class ArgyrisDualSet(dual_set.DualSet):
+    def __init__(self, ref_el, degree):
         entity_ids = {}
         nodes = []
         cur = 0
@@ -38,58 +40,55 @@ class ArgyrisDualSet( dual_set.DualSet ):
         # get jet at each vertex
 
         entity_ids[0] = {}
-        for v in sorted( top[0] ):
-            nodes.append( pe( ref_el, verts[v] ) )
+        for v in sorted(top[0]):
+            nodes.append(pe(ref_el, verts[v]))
 
             # first derivatives
-            for i in range( sd ):
+            for i in range(sd):
                 alpha = [0] * sd
                 alpha[i] = 1
-                nodes.append( pd( ref_el, verts[v], alpha ) )
+                nodes.append(pd(ref_el, verts[v], alpha))
 
             # second derivatives
-            alphas = [ [2, 0], [0, 2], [1, 1] ]
+            alphas = [[2, 0], [0, 2], [1, 1]]
             for alpha in alphas:
-                nodes.append( pd( ref_el, verts[v], alpha ) )
+                nodes.append(pd(ref_el, verts[v], alpha))
 
-
-            entity_ids[0][v] = list(range(cur, cur+6))
+            entity_ids[0][v] = list(range(cur, cur + 6))
             cur += 6
 
         # edge dof
         entity_ids[1] = {}
-        for e in sorted( top[1] ):
+        for e in sorted(top[1]):
             # normal derivatives at degree - 4 points on each edge
-            ndpts = ref_el.make_points( 1, e, degree - 3 )
-            ndnds = [ pnd( ref_el, e, pt ) for pt in ndpts ]
-            nodes.extend( ndnds )
+            ndpts = ref_el.make_points(1, e, degree - 3)
+            ndnds = [pnd(ref_el, e, pt) for pt in ndpts]
+            nodes.extend(ndnds)
             entity_ids[1][e] = list(range(cur, cur + len(ndpts)))
-            cur += len( ndpts )
+            cur += len(ndpts)
 
             # point value at degree-5 points on each edge
             if degree > 5:
-                ptvalpts = ref_el.make_points( 1, e, degree - 4 )
-                ptvalnds = [ pe( ref_el, pt ) for pt in ptvalpts ]
-                nodes.extend( ptvalnds )
-                entity_ids[1][e] += list(range(cur, cur+len(ptvalpts)))
-                cur += len( ptvalpts )
+                ptvalpts = ref_el.make_points(1, e, degree - 4)
+                ptvalnds = [pe(ref_el, pt) for pt in ptvalpts]
+                nodes.extend(ptvalnds)
+                entity_ids[1][e] += list(range(cur, cur + len(ptvalpts)))
+                cur += len(ptvalpts)
 
         # internal dof
         entity_ids[2] = {}
         if degree > 5:
-            internalpts = ref_el.make_points( 2, 0, degree - 3 )
-            internalnds = [ pe( ref_el, pt ) for pt in internalpts ]
-            nodes.extend( internalnds )
-            entity_ids[2][0] = list(range(cur, cur+len(internalpts)))
+            internalpts = ref_el.make_points(2, 0, degree - 3)
+            internalnds = [pe(ref_el, pt) for pt in internalpts]
+            nodes.extend(internalnds)
+            entity_ids[2][0] = list(range(cur, cur + len(internalpts)))
             cur += len(internalpts)
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(ArgyrisDualSet, self).__init__(nodes, ref_el, entity_ids)
+
 
-class QuinticArgyrisDualSet( dual_set.DualSet ):
-    """The dual basis for Lagrange elements.  This class works for
-    simplices of any dimension.  Nodes are point evaluation at
-    equispaced points."""
-    def __init__( self, ref_el ):
+class QuinticArgyrisDualSet(dual_set.DualSet):
+    def __init__(self, ref_el):
         entity_ids = {}
         nodes = []
         cur = 0
@@ -107,63 +106,48 @@ class QuinticArgyrisDualSet( dual_set.DualSet ):
         # get jet at each vertex
 
         entity_ids[0] = {}
-        for v in sorted( top[0] ):
-            nodes.append( functional.PointEvaluation( ref_el, verts[v] ) )
+        for v in sorted(top[0]):
+            nodes.append(functional.PointEvaluation(ref_el, verts[v]))
 
             # first derivatives
-            for i in range( sd ):
+            for i in range(sd):
                 alpha = [0] * sd
                 alpha[i] = 1
-                nodes.append( pd( ref_el, verts[v], alpha ) )
+                nodes.append(pd(ref_el, verts[v], alpha))
 
             # second derivatives
-            alphas = [ [2, 0], [0, 2], [1, 1] ]
+            alphas = [[2, 0], [0, 2], [1, 1]]
             for alpha in alphas:
-                nodes.append( pd( ref_el, verts[v], alpha ) )
+                nodes.append(pd(ref_el, verts[v], alpha))
 
-
-            entity_ids[0][v] = list(range(cur, cur+6))
+            entity_ids[0][v] = list(range(cur, cur + 6))
             cur += 6
 
         # edge dof -- normal at each edge midpoint
         entity_ids[1] = {}
-        for e in sorted( top[1] ):
-            pt = ref_el.make_points( 1, e, 2 )[0]
-            n = functional.PointNormalDerivative( ref_el, e, pt )
-            nodes.append( n )
+        for e in sorted(top[1]):
+            pt = ref_el.make_points(1, e, 2)[0]
+            n = functional.PointNormalDerivative(ref_el, e, pt)
+            nodes.append(n)
             entity_ids[1][e] = [cur]
             cur += 1
 
+        super(QuinticArgyrisDualSet, self).__init__(nodes, ref_el, entity_ids)
+
 
+class Argyris(finite_element.CiarletElement):
+    """The Argyris finite element."""
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+    def __init__(self, ref_el, degree):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = ArgyrisDualSet(ref_el, degree)
+        super(Argyris, self).__init__(poly_set, dual, degree)
 
 
-class Argyris( finite_element.FiniteElement ):
+class QuinticArgyris(finite_element.CiarletElement):
     """The Argyris finite element."""
-    def __init__( self, ref_el, degree ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, degree )
-        dual = ArgyrisDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree )
 
-class QuinticArgyris( finite_element.FiniteElement ):
-    """The Argyris finite element."""
-    def __init__( self, ref_el ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, 5 )
-        dual = QuinticArgyrisDualSet( ref_el  )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, 5 )
-
-if __name__=="__main__":
-    from . import reference_element
-    from . import lagrange
-    T = reference_element.DefaultTriangle()
-    for k in range(5, 11):
-        U = Argyris( T, k )
-        U2 = lagrange.Lagrange( T, k )
-        c = U.get_nodal_basis().get_coeffs()
-        sigma = numpy.linalg.svd( c, compute_uv = 0)
-        print("Argyris ", k, max(sigma) / min(sigma))
-        c = U2.get_nodal_basis().get_coeffs()
-        sigma = numpy.linalg.svd( c, compute_uv = 0)
-        print("Lagrange ", k, max(sigma) / min(sigma ))
-        print()
+    def __init__(self, ref_el):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, 5)
+        dual = QuinticArgyrisDualSet(ref_el)
+        super(QuinticArgyris, self).__init__(poly_set, dual, 5)
diff --git a/FIAT/asci2vtk2d.py b/FIAT/asci2vtk2d.py
deleted file mode 100644
index 5fbe5df..0000000
--- a/FIAT/asci2vtk2d.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-#!/usr/bin/env python
-
-# 2d mode: x y z, z = f(x,y)
-import sys
-
-if len(sys.argv) > 1:
-    filename = sys.argv[1]
-    print(filename)
-    base = filename.split(".")[0]
-    output = "%s.vtk" % (base,)
-    print("output to %s" % output)
-else:
-    print("python asci2vtk.py foo")
-    sys.exit(0)
-
-
-fin = open( filename, "r" )
-
-coords = [ ]
-
-for line in fin:
-    coords.append( line.split() )
-
-fin.close()
-
-n = len( coords )
-
-print("%s points" % (str(n),))
-
-
-fout = open( output, "w" )
-fout.write("""# vtk DataFile Version 2.0
-points
-ASCII
-DATASET UNSTRUCTURED_GRID
-POINTS %s float\n""" % str(n))
-
-for c in coords:
-    fout.write("%s %s %s\n" % (c[0], c[1], 0))
-
-fout.write("CELLS %s %s\n" % (n, 2*n))
-for i in range( n ):
-    fout.write("1 %s\n" % i)
-
-fout.write("CELL_TYPES %s\n" % (n,))
-for i in range( n ):
-    fout.write("1\n")
-
-fout.write("POINT_DATA %s\n" % (n,))
-fout.write("""SCALARS Z float 1
-LOOKUP_TABLE default\n""")
-
-for i in range( n ):
-    fout.write("%s" % coords[i][2])
-
-fout.close()
diff --git a/FIAT/asci2vtk3d.py b/FIAT/asci2vtk3d.py
deleted file mode 100644
index 12f8584..0000000
--- a/FIAT/asci2vtk3d.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-#!/usr/bin/env python
-
-# 3d mode: x y z f, f = f(x,y,z)
-import sys
-
-if len(sys.argv) > 1:
-    filename = sys.argv[1]
-    print(filename)
-    base = filename.split(".")[0]
-    output = "%s.vtk" % (base,)
-    print("output to %s" % (output,))
-else:
-    print("python asci2vtk3d.py foo")
-    sys.exit(0)
-
-
-fin = open( filename, "r" )
-
-coords = [ ]
-
-for line in fin:
-    coords.append( line.split() )
-
-fin.close()
-
-n = len( coords )
-
-print("%s points" % (str(n),))
-
-
-fout = open( output, "w" )
-fout.write("""# vtk DataFile Version 2.0
-points
-ASCII
-DATASET UNSTRUCTURED_GRID
-POINTS %s float\n""" % (str(n),))
-
-for c in coords:
-    fout.write("%s %s %s\n" % (c[0], c[1], c[2]))
-
-fout.write("CELLS %s %s\n" % (n, 2*n))
-for i in range( n ):
-    fout.write("1 %s\n" % (i,))
-
-fout.write("CELL_TYPES %s\n" % (n,))
-for i in range( n ):
-    fout.write("1\n")
-
-fout.write("POINT_DATA %s\n" % (n,))
-fout.write("""SCALARS Z float 1
-LOOKUP_TABLE default\n""")
-
-for i in range( n ):
-    fout.write("%s\n", ncoords[i][3])
-
-fout.close()
diff --git a/FIAT/brezzi_douglas_fortin_marini.py b/FIAT/brezzi_douglas_fortin_marini.py
index fcfb83b..4e60613 100644
--- a/FIAT/brezzi_douglas_fortin_marini.py
+++ b/FIAT/brezzi_douglas_fortin_marini.py
@@ -1,10 +1,13 @@
-from . import finite_element, quadrature, functional, \
-    dual_set, reference_element, polynomial_set, lagrange
+from __future__ import absolute_import, print_function, division
+
+from FIAT import (finite_element, functional, dual_set,
+                  polynomial_set, lagrange)
 
 import numpy
 
-class BDFMDualSet( dual_set.DualSet ):
-    def __init__( self, ref_el, degree ):
+
+class BDFMDualSet(dual_set.DualSet):
+    def __init__(self, ref_el, degree):
 
         # Initialize containers for map: mesh_entity -> dof number and
         # dual basis
@@ -14,111 +17,103 @@ class BDFMDualSet( dual_set.DualSet ):
         sd = ref_el.get_spatial_dimension()
         t = ref_el.get_topology()
 
-
         # Define each functional for the dual set
         # codimension 1 facet normals.
         # note this will die for degree greater than 1.
-        for i in range( len( t[sd-1] ) ):
-            pts_cur = ref_el.make_points( sd - 1, i, sd + degree )
-            for j in range( len( pts_cur ) ):
+        for i in range(len(t[sd - 1])):
+            pts_cur = ref_el.make_points(sd - 1, i, sd + degree)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointScaledNormalEvaluation( ref_el, i, \
-                                                            pt_cur )
-                nodes.append( f )
+                f = functional.PointScaledNormalEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # codimension 1 facet tangents.
         # because the tangent component is discontinuous, these actually
         # count as internal nodes.
-        tangent_count=0
-        for i in range( len( t[sd-1] ) ):
-            pts_cur = ref_el.make_points( sd - 1, i, sd + degree - 1 )
-            tangent_count+=len( pts_cur )
-            for j in range( len( pts_cur ) ):
+        tangent_count = 0
+        for i in range(len(t[sd - 1])):
+            pts_cur = ref_el.make_points(sd - 1, i, sd + degree - 1)
+            tangent_count += len(pts_cur)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointEdgeTangentEvaluation( ref_el, i, \
-                                                             pt_cur )
-                nodes.append( f )
+                f = functional.PointEdgeTangentEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # sets vertices (and in 3d, edges) to have no nodes
-        for i in range( sd - 1 ):
+        for i in range(sd - 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         cur = 0
 
         # set codimension 1 (edges 2d, faces 3d) dof
-        pts_facet_0 = ref_el.make_points( sd - 1, 0, sd + degree )
-        pts_per_facet = len( pts_facet_0 )
+        pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree)
+        pts_per_facet = len(pts_facet_0)
 
-        entity_ids[sd-1] = {}
-        for i in range( len( t[sd-1] ) ):
-            entity_ids[sd-1][i] = list(range( cur, cur + pts_per_facet))
+        entity_ids[sd - 1] = {}
+        for i in range(len(t[sd - 1])):
+            entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet))
             cur += pts_per_facet
 
         # internal nodes
-        entity_ids[sd] = {0: list(range(cur, cur+tangent_count))}
-        cur+=tangent_count
-    
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        entity_ids[sd] = {0: list(range(cur, cur + tangent_count))}
+        cur += tangent_count
+
+        super(BDFMDualSet, self).__init__(nodes, ref_el, entity_ids)
+
 
 def BDFMSpace(ref_el, order):
     sd = ref_el.get_spatial_dimension()
-    if sd !=2:
+    if sd != 2:
         raise Exception("BDFM_k elements only valid for dim 2")
     # Note that order will be 2.
 
     # Linear vector valued space. Since the embedding degree of this element
     # is 2, this is implemented by taking the quadratic space and selecting
     # the linear polynomials.
-    vec_poly_set = polynomial_set.ONPolynomialSet( ref_el, order, (sd,) )
+    vec_poly_set = polynomial_set.ONPolynomialSet(ref_el, order, (sd,))
     # Linears are the first three polynomials in each dimension.
     vec_poly_set = vec_poly_set.take([0, 1, 2, 6, 7, 8])
 
     # Scalar quadratic Lagrange element.
     lagrange_ele = lagrange.Lagrange(ref_el, order)
     # Select the dofs associated with the edges.
-    edge_dofs_dict=lagrange_ele.dual.get_entity_ids()[sd-1]
-    edge_dofs=numpy.array([(edge, dof) for edge, dofs in list(edge_dofs_dict.items())
-                           for dof in dofs])
+    edge_dofs_dict = lagrange_ele.dual.get_entity_ids()[sd - 1]
+    edge_dofs = numpy.array([(edge, dof)
+                             for edge, dofs in list(edge_dofs_dict.items())
+                             for dof in dofs])
+
+    tangent_polys = lagrange_ele.poly_set.take(edge_dofs[:, 1])
+    new_coeffs = numpy.zeros((tangent_polys.get_num_members(), sd, tangent_polys.coeffs.shape[-1]))
 
-    tangent_polys=lagrange_ele.poly_set.take(edge_dofs[:, 1])
-    new_coeffs=numpy.zeros((tangent_polys.get_num_members(), sd, tangent_polys.coeffs.shape[-1]))
-    
     # Outer product of the tangent vectors with the quadratic edge polynomials.
     for i, (edge, dof) in enumerate(edge_dofs):
-        tangent=ref_el.compute_edge_tangent(edge)
-
-        new_coeffs[i,:,:]=numpy.outer(tangent, tangent_polys.coeffs[i,:])
-    
-    bubble_set = polynomial_set.PolynomialSet( ref_el, \
-                                               order, \
-                                               order, \
-                                               vec_poly_set.get_expansion_set(), \
-                                               new_coeffs, \
-                                               vec_poly_set.get_dmats() )    
-
-    element_set =  polynomial_set.polynomial_set_union_normalized( bubble_set, vec_poly_set )
+        tangent = ref_el.compute_edge_tangent(edge)
+
+        new_coeffs[i, :, :] = numpy.outer(tangent, tangent_polys.coeffs[i, :])
+
+    bubble_set = polynomial_set.PolynomialSet(ref_el,
+                                              order,
+                                              order,
+                                              vec_poly_set.get_expansion_set(),
+                                              new_coeffs,
+                                              vec_poly_set.get_dmats())
+
+    element_set = polynomial_set.polynomial_set_union_normalized(bubble_set, vec_poly_set)
     return element_set
-    
 
-class BrezziDouglasFortinMarini( finite_element.FiniteElement ):
+
+class BrezziDouglasFortinMarini(finite_element.CiarletElement):
     """The BDFM element"""
+
     def __init__(self, ref_el, degree):
 
         if degree != 2:
             raise Exception("BDFM_k elements only valid for k == 2")
 
         poly_set = BDFMSpace(ref_el, degree)
-        dual = BDFMDualSet(ref_el, degree-1)
-        finite_element.FiniteElement.__init__(self, poly_set, dual, degree,
-                                               mapping="contravariant piola")
-
-        return
-
-if __name__=="__main__":
-    T = reference_element.UFCTriangle()
-
-    BDFM = BrezziDouglasFortinMarini(T, 2)
-
-    
+        dual = BDFMDualSet(ref_el, degree - 1)
+        formdegree = ref_el.get_spatial_dimension() - 1
+        super(BrezziDouglasFortinMarini, self).__init__(poly_set, dual, degree, formdegree,
+                                                        mapping="contravariant piola")
diff --git a/FIAT/brezzi_douglas_marini.py b/FIAT/brezzi_douglas_marini.py
index c3788cc..f5117aa 100644
--- a/FIAT/brezzi_douglas_marini.py
+++ b/FIAT/brezzi_douglas_marini.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,11 +16,14 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, raviart_thomas, quadrature, functional, \
-    dual_set, reference_element, polynomial_set, nedelec
+from __future__ import absolute_import, print_function, division
 
-class BDMDualSet( dual_set.DualSet ):
-    def __init__( self, ref_el, degree ):
+from FIAT import (finite_element, quadrature, functional, dual_set,
+                  polynomial_set, nedelec)
+
+
+class BDMDualSet(dual_set.DualSet):
+    def __init__(self, ref_el, degree):
 
         # Initialize containers for map: mesh_entity -> dof number and
         # dual basis
@@ -29,78 +33,67 @@ class BDMDualSet( dual_set.DualSet ):
         sd = ref_el.get_spatial_dimension()
         t = ref_el.get_topology()
 
-
         # Define each functional for the dual set
         # codimension 1 facets
-        for i in range( len( t[sd-1] ) ):
-            pts_cur = ref_el.make_points( sd - 1, i, sd + degree )
-            for j in range( len( pts_cur ) ):
+        for i in range(len(t[sd - 1])):
+            pts_cur = ref_el.make_points(sd - 1, i, sd + degree)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointScaledNormalEvaluation( ref_el, i, \
-                                                            pt_cur )
-                nodes.append( f )
+                f = functional.PointScaledNormalEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # internal nodes
         if degree > 1:
-            Q = quadrature.make_quadrature( ref_el, 2 * (degree + 1) )
+            Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1))
             qpts = Q.get_points()
-            Nedel = nedelec.Nedelec( ref_el, degree - 1 )
+            Nedel = nedelec.Nedelec(ref_el, degree - 1)
             Nedfs = Nedel.get_nodal_basis()
-            zero_index = tuple( [ 0 for i in range( sd ) ] )
-            Ned_at_qpts = Nedfs.tabulate( qpts )[ zero_index ]
+            zero_index = tuple([0 for i in range(sd)])
+            Ned_at_qpts = Nedfs.tabulate(qpts)[zero_index]
 
-            for i in range( len( Ned_at_qpts ) ):
-                phi_cur = Ned_at_qpts[i,:]
-                l_cur = functional.FrobeniusIntegralMoment( ref_el, Q, \
-                                                                phi_cur )
+            for i in range(len(Ned_at_qpts)):
+                phi_cur = Ned_at_qpts[i, :]
+                l_cur = functional.FrobeniusIntegralMoment(ref_el, Q, phi_cur)
                 nodes.append(l_cur)
 
         # sets vertices (and in 3d, edges) to have no nodes
-        for i in range( sd - 1 ):
+        for i in range(sd - 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         cur = 0
 
         # set codimension 1 (edges 2d, faces 3d) dof
-        pts_facet_0 = ref_el.make_points( sd - 1, 0, sd + degree )
-        pts_per_facet = len( pts_facet_0 )
+        pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree)
+        pts_per_facet = len(pts_facet_0)
 
-        entity_ids[sd-1] = {}
-        for i in range( len( t[sd-1] ) ):
-            entity_ids[sd-1][i] = list(range( cur, cur + pts_per_facet))
+        entity_ids[sd - 1] = {}
+        for i in range(len(t[sd - 1])):
+            entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet))
             cur += pts_per_facet
 
         # internal nodes, if applicable
         entity_ids[sd] = {0: []}
 
         if degree > 1:
-            num_internal_nodes = len( Ned_at_qpts )
-            entity_ids[sd][0] = list(range( cur, cur + num_internal_nodes))
+            num_internal_nodes = len(Ned_at_qpts)
+            entity_ids[sd][0] = list(range(cur, cur + num_internal_nodes))
 
+        super(BDMDualSet, self).__init__(nodes, ref_el, entity_ids)
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
 
-class BrezziDouglasMarini( finite_element.FiniteElement ):
+class BrezziDouglasMarini(finite_element.CiarletElement):
     """The BDM element"""
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
 
         if degree < 1:
             raise Exception("BDM_k elements only valid for k >= 1")
 
         sd = ref_el.get_spatial_dimension()
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, degree, (sd,) )
-        dual = BDMDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree,
-                                               mapping="contravariant piola")
-
-        return
-
-if __name__=="__main__":
-    T = reference_element.UFCTetrahedron()
-
-    for k in range(1, 3):
-        print(k)
-        BDM = BrezziDouglasMarini( T, k )
-        print()
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd, ))
+        dual = BDMDualSet(ref_el, degree)
+        formdegree = sd - 1  # (n-1)-form
+        super(BrezziDouglasMarini, self).__init__(poly_set, dual, degree, formdegree,
+                                                  mapping="contravariant piola")
diff --git a/FIAT/bubble.py b/FIAT/bubble.py
new file mode 100644
index 0000000..bb3ee80
--- /dev/null
+++ b/FIAT/bubble.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2013 Andrew T. T. McRae (Imperial College London)
+# Copyright (C) 2015 Jan Blechta
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+from FIAT.lagrange import Lagrange
+from FIAT.restricted import RestrictedElement
+
+
+class Bubble(RestrictedElement):
+    """The Bubble finite element: the interior dofs of the Lagrange FE"""
+
+    def __init__(self, ref_el, degree):
+        element = Lagrange(ref_el, degree)
+
+        cell_dim = ref_el.get_dimension()
+        assert cell_dim == max(element.entity_dofs().keys())
+        cell_entity_dofs = element.entity_dofs()[cell_dim][0]
+        if len(cell_entity_dofs) == 0:
+            raise RuntimeError('Bubble element of degree %d has no dofs' % degree)
+
+        super(Bubble, self).__init__(element, indices=cell_entity_dofs)
diff --git a/FIAT/crouzeix_raviart.py b/FIAT/crouzeix_raviart.py
index 90f3e22..bed4aef 100644
--- a/FIAT/crouzeix_raviart.py
+++ b/FIAT/crouzeix_raviart.py
@@ -20,7 +20,10 @@
 #
 # Last changed: 2010-01-28
 
-from . import finite_element, polynomial_set, dual_set, functional
+from __future__ import absolute_import, print_function, division
+
+from FIAT import finite_element, polynomial_set, dual_set, functional
+
 
 def _initialize_entity_ids(topology):
     entity_ids = {}
@@ -30,6 +33,7 @@ def _initialize_entity_ids(topology):
             entity_ids[i][j] = []
     return entity_ids
 
+
 class CrouzeixRaviartDualSet(dual_set.DualSet):
     """Dual basis for Crouzeix-Raviart element (linears continuous at
     boundary midpoints)."""
@@ -42,22 +46,23 @@ class CrouzeixRaviartDualSet(dual_set.DualSet):
 
         # Initialize empty nodes and entity_ids
         entity_ids = _initialize_entity_ids(topology)
-        nodes = [None for i in list(topology[d-1].keys())]
+        nodes = [None for i in list(topology[d - 1].keys())]
 
         # Construct nodes and entity_ids
-        for i in topology[d-1]:
+        for i in topology[d - 1]:
 
             # Construct midpoint
-            x = cell.make_points(d-1, i, d)[0]
+            x = cell.make_points(d - 1, i, d)[0]
 
             # Degree of freedom number i is evaluation at midpoint
             nodes[i] = functional.PointEvaluation(cell, x)
-            entity_ids[d-1][i] += [i]
+            entity_ids[d - 1][i] += [i]
 
         # Initialize super-class
-        dual_set.DualSet.__init__(self, nodes, cell, entity_ids)
+        super(CrouzeixRaviartDualSet, self).__init__(nodes, cell, entity_ids)
+
 
-class CrouzeixRaviart(finite_element.FiniteElement):
+class CrouzeixRaviart(finite_element.CiarletElement):
     """The Crouzeix-Raviart finite element:
 
     K:                 Triangle/Tetrahedron
@@ -75,17 +80,4 @@ class CrouzeixRaviart(finite_element.FiniteElement):
         # FiniteElement
         space = polynomial_set.ONPolynomialSet(cell, 1)
         dual = CrouzeixRaviartDualSet(cell, 1)
-        finite_element.FiniteElement.__init__(self, space, dual, 1)
-
-
-if __name__ == "__main__":
-
-    from . import reference_element
-
-    cells = [reference_element.UFCTriangle(),
-             reference_element.UFCTetrahedron()]
-    for cell in cells:
-        print("Checking CrouzeixRaviart(cell, 1)")
-        element = CrouzeixRaviart(cell, 1)
-        print([L.pt_dict for L in element.dual_basis()])
-        print()
+        super(CrouzeixRaviart, self).__init__(space, dual, 1)
diff --git a/FIAT/discontinuous.py b/FIAT/discontinuous.py
new file mode 100644
index 0000000..db11a63
--- /dev/null
+++ b/FIAT/discontinuous.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2014 Andrew T. T. McRae (Imperial College London)
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+from FIAT.finite_element import CiarletElement
+from FIAT.dual_set import DualSet
+
+
+class DiscontinuousElement(CiarletElement):
+    """A copy of an existing element where all dofs are associated with the cell"""
+
+    def __init__(self, element):
+        self._element = element
+        new_entity_ids = {}
+        topology = element.get_reference_element().get_topology()
+        for dim in sorted(topology):
+            new_entity_ids[dim] = {}
+            for ent in sorted(topology[dim]):
+                new_entity_ids[dim][ent] = []
+
+        new_entity_ids[dim][0] = list(range(element.space_dimension()))
+        # re-initialise the dual, so entity_closure_dofs is recalculated
+        self.dual = DualSet(element.dual_basis(), element.get_reference_element(), new_entity_ids)
+
+        # fully discontinuous
+        self.formdegree = element.get_reference_element().get_spatial_dimension()
+
+    def degree(self):
+        "Return the degree of the (embedding) polynomial space."
+        return self._element.degree()
+
+    def get_reference_element(self):
+        "Return the reference element for the finite element."
+        return self._element.get_reference_element()
+
+    def get_nodal_basis(self):
+        """Return the nodal basis, encoded as a PolynomialSet object,
+        for the finite element."""
+        return self._element.get_nodal_basis()
+
+    def get_order(self):
+        "Return the order of the element (may be different from the degree)"
+        return self._element.get_order()
+
+    def get_coeffs(self):
+        """Return the expansion coefficients for the basis of the
+        finite element."""
+        return self._element.get_coeffs()
+
+    def mapping(self):
+        """Return a list of appropriate mappings from the reference
+        element to a physical element for each basis function of the
+        finite element."""
+        return self._element.mapping()
+
+    def num_sub_elements(self):
+        "Return the number of sub-elements."
+        return self._element.num_sub_elements()
+
+    def space_dimension(self):
+        "Return the dimension of the finite element space."
+        return self._element.space_dimension()
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points."""
+        return self._element.tabulate(order, points, entity)
+
+    def value_shape(self):
+        "Return the value shape of the finite element functions."
+        return self._element.value_shape()
+
+    def dmats(self):
+        """Return dmats: expansion coefficients for basis function
+        derivatives."""
+        return self._element.dmats()
+
+    def get_num_members(self, arg):
+        "Return number of members of the expansion set."
+        return self._element.get_num_members()
diff --git a/FIAT/discontinuous_lagrange.py b/FIAT/discontinuous_lagrange.py
index 2b53090..e8526bd 100644
--- a/FIAT/discontinuous_lagrange.py
+++ b/FIAT/discontinuous_lagrange.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,14 +16,18 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, polynomial_set, dual_set, functional, P0
+from __future__ import absolute_import, print_function, division
 
-class DiscontinuousLagrangeDualSet( dual_set.DualSet ):
+from FIAT import finite_element, polynomial_set, dual_set, functional, P0
+
+
+class DiscontinuousLagrangeDualSet(dual_set.DualSet):
     """The dual basis for Lagrange elements.  This class works for
     simplices of any dimension.  Nodes are point evaluation at
     equispaced points.  This is the discontinuous version where
     all nodes are topologically associated with the cell itself"""
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         entity_ids = {}
         nodes = []
 
@@ -31,44 +36,34 @@ class DiscontinuousLagrangeDualSet( dual_set.DualSet ):
         top = ref_el.get_topology()
 
         cur = 0
-        for dim in sorted( top ):
+        for dim in sorted(top):
             entity_ids[dim] = {}
-            for entity in sorted( top[dim] ):
-                pts_cur = ref_el.make_points( dim, entity, degree )
-                nodes_cur = [ functional.PointEvaluation( ref_el, x ) \
-                              for x in pts_cur ]
-                nnodes_cur = len( nodes_cur )
-                nodes +=  nodes_cur
-                entity_ids[dim][entity]=[]
+            for entity in sorted(top[dim]):
+                pts_cur = ref_el.make_points(dim, entity, degree)
+                nodes_cur = [functional.PointEvaluation(ref_el, x)
+                             for x in pts_cur]
+                nnodes_cur = len(nodes_cur)
+                nodes += nodes_cur
+                entity_ids[dim][entity] = []
                 cur += nnodes_cur
 
         entity_ids[dim][0] = list(range(len(nodes)))
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(DiscontinuousLagrangeDualSet, self).__init__(nodes, ref_el, entity_ids)
+
 
-class HigherOrderDiscontinuousLagrange( finite_element.FiniteElement ):
+class HigherOrderDiscontinuousLagrange(finite_element.CiarletElement):
     """The discontinuous Lagrange finite element.  It is what it is."""
-    def __init__( self, ref_el, degree ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, degree )
-        dual = DiscontinuousLagrangeDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree )
 
-def DiscontinuousLagrange( ref_el, degree ):
-    if degree == 0:
-        return P0.P0( ref_el )
-    else:
-        return HigherOrderDiscontinuousLagrange( ref_el, degree )
+    def __init__(self, ref_el, degree):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = DiscontinuousLagrangeDualSet(ref_el, degree)
+        formdegree = ref_el.get_spatial_dimension()  # n-form
+        super(HigherOrderDiscontinuousLagrange, self).__init__(poly_set, dual, degree, formdegree)
 
-if __name__=="__main__":
-    from . import reference_element
-    T = reference_element.DefaultTetrahedron()
-    for k in range(2, 3):
-        U = DiscontinuousLagrange( T, k )
 
-    Ufs = U.get_nodal_basis()
-    pts = T.make_lattice( k )
-    print(pts)
-    for foo, bar in list(Ufs.tabulate( pts, 1 ).items()):
-        print(foo)
-        print(bar)
-        print()
+def DiscontinuousLagrange(ref_el, degree):
+    if degree == 0:
+        return P0.P0(ref_el)
+    else:
+        return HigherOrderDiscontinuousLagrange(ref_el, degree)
diff --git a/FIAT/discontinuous_raviart_thomas.py b/FIAT/discontinuous_raviart_thomas.py
index 8474f16..1947ed7 100644
--- a/FIAT/discontinuous_raviart_thomas.py
+++ b/FIAT/discontinuous_raviart_thomas.py
@@ -17,18 +17,19 @@
 #
 # Modified by Jan Blechta 2014
 
-from . import expansions, polynomial_set, quadrature, reference_element, dual_set, \
-    quadrature, finite_element, functional
-import numpy
-from functools import reduce
-from .raviart_thomas import RTSpace
+from __future__ import absolute_import, print_function, division
 
-class DRTDualSet( dual_set.DualSet ):
+from FIAT import dual_set, finite_element, functional
+from FIAT.raviart_thomas import RTSpace
+
+
+class DRTDualSet(dual_set.DualSet):
     """Dual basis for Raviart-Thomas elements consisting of point
     evaluation of normals on facets of codimension 1 and internal
     moments against polynomials. This is the discontinuous version
     where all nodes are topologically associated with the cell itself"""
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         entity_ids = {}
         nodes = []
 
@@ -36,46 +37,46 @@ class DRTDualSet( dual_set.DualSet ):
         t = ref_el.get_topology()
 
         # codimension 1 facets
-        for i in range( len( t[sd-1] ) ):
-            pts_cur = ref_el.make_points( sd - 1, i, sd + degree )
-            for j in range( len( pts_cur ) ):
+        for i in range(len(t[sd - 1])):
+            pts_cur = ref_el.make_points(sd - 1, i, sd + degree)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointScaledNormalEvaluation( ref_el, i, \
-                                                            pt_cur )
-                nodes.append( f )
+                f = functional.PointScaledNormalEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # internal nodes.  Let's just use points at a lattice
         if degree > 0:
             cpe = functional.ComponentPointEvaluation
-            pts = ref_el.make_points( sd, 0, degree + sd )
-            for d in range( sd ):
-                for i in range( len( pts ) ):
-                    l_cur = cpe( ref_el, d, (sd,), pts[i] )
-                    nodes.append( l_cur )
+            pts = ref_el.make_points(sd, 0, degree + sd)
+            for d in range(sd):
+                for i in range(len(pts)):
+                    l_cur = cpe(ref_el, d, (sd,), pts[i])
+                    nodes.append(l_cur)
 
         # sets vertices (and in 3d, edges) to have no nodes
-        for i in range( sd - 1 ):
+        for i in range(sd - 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         # set codimension 1 (edges 2d, faces 3d) to have no dofs
-        entity_ids[sd-1] = {}
-        for i in range( len( t[sd-1] ) ):
-            entity_ids[sd-1][i] = []
+        entity_ids[sd - 1] = {}
+        for i in range(len(t[sd - 1])):
+            entity_ids[sd - 1][i] = []
 
-        # cell dofs 
+        # cell dofs
         entity_ids[sd] = {0: list(range(len(nodes)))}
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(DRTDualSet, self).__init__(nodes, ref_el, entity_ids)
 
 
-class DiscontinuousRaviartThomas( finite_element.FiniteElement ):
+class DiscontinuousRaviartThomas(finite_element.CiarletElement):
     """The discontinuous Raviart-Thomas finite element"""
-    def __init__( self, ref_el, q ):
+
+    def __init__(self, ref_el, q):
 
         degree = q - 1
-        poly_set = RTSpace( ref_el, degree )
-        dual = DRTDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree,
-                                               mapping="contravariant piola")
+        poly_set = RTSpace(ref_el, degree)
+        dual = DRTDualSet(ref_el, degree)
+        super(DiscontinuousRaviartThomas, self).__init__(poly_set, dual, degree,
+                                                         mapping="contravariant piola")
diff --git a/FIAT/discontinuous_taylor.py b/FIAT/discontinuous_taylor.py
new file mode 100644
index 0000000..b120f86
--- /dev/null
+++ b/FIAT/discontinuous_taylor.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Modified by Colin Cotter (Imperial College London)
+#             David Ham (Imperial College London)
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+from FIAT import finite_element, polynomial_set, dual_set, functional, P0, quadrature
+from FIAT.polynomial_set import mis
+import numpy
+
+
+class DiscontinuousTaylorDualSet(dual_set.DualSet):
+    """The dual basis for Taylor elements.  This class works for
+    intervals.  Nodes are function and derivative evaluation
+    at the midpoint."""
+
+    def __init__(self, ref_el, degree):
+        nodes = []
+        dim = ref_el.get_spatial_dimension()
+
+        Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1))
+
+        f_at_qpts = numpy.ones(len(Q.wts))
+        nodes.append(functional.IntegralMoment(ref_el, Q, f_at_qpts))
+
+        vertices = ref_el.get_vertices()
+        midpoint = tuple(sum(numpy.array(vertices)) / len(vertices))
+        for k in range(1, degree + 1):
+            # Loop over all multi-indices of degree k.
+            for alpha in mis(dim, k):
+                nodes.append(functional.PointDerivative(ref_el, midpoint, alpha))
+
+        entity_ids = {d: {e: [] for e in ref_el.sub_entities[d]}
+                      for d in range(dim + 1)}
+        entity_ids[dim][0] = list(range(len(nodes)))
+
+        super(DiscontinuousTaylorDualSet, self).__init__(nodes, ref_el, entity_ids)
+
+
+class HigherOrderDiscontinuousTaylor(finite_element.CiarletElement):
+    """The discontinuous Taylor finite element. Use a Taylor basis for DG."""
+
+    def __init__(self, ref_el, degree):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = DiscontinuousTaylorDualSet(ref_el, degree)
+        formdegree = ref_el.get_spatial_dimension()  # n-form
+        super(HigherOrderDiscontinuousTaylor, self).__init__(poly_set, dual, degree, formdegree)
+
+
+def DiscontinuousTaylor(ref_el, degree):
+    if degree == 0:
+        return P0.P0(ref_el)
+    else:
+        return HigherOrderDiscontinuousTaylor(ref_el, degree)
diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py
index 83bfbdf..13b268e 100644
--- a/FIAT/dual_set.py
+++ b/FIAT/dual_set.py
@@ -15,36 +15,54 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import, print_function, division
+
 import numpy
+from six import iteritems
+
 
-class DualSet:
-    def __init__( self, nodes, ref_el, entity_ids ):
+class DualSet(object):
+    def __init__(self, nodes, ref_el, entity_ids):
         self.nodes = nodes
         self.ref_el = ref_el
         self.entity_ids = entity_ids
-        return
 
-    def get_nodes( self ):
+        # Compute the nodes on the closure of each sub_entity.
+        self.entity_closure_ids = {}
+        for dim, entities in iteritems(ref_el.sub_entities):
+            self.entity_closure_ids[dim] = {}
+
+            for e, sub_entities in iteritems(entities):
+                ids = []
+
+                for d, se in sub_entities:
+                    ids += self.entity_ids[d][se]
+                self.entity_closure_ids[d][e] = ids
+
+    def get_nodes(self):
         return self.nodes
 
-    def get_entity_ids( self ):
+    def get_entity_closure_ids(self):
+        return self.entity_closure_ids
+
+    def get_entity_ids(self):
         return self.entity_ids
 
-    def get_reference_element( self ):
+    def get_reference_element(self):
         return self.ref_el
 
-    def to_riesz( self, poly_set ):
+    def to_riesz(self, poly_set):
 
         tshape = self.nodes[0].target_shape
-        num_nodes = len( self.nodes )
-        es = poly_set.get_expansion_set( )
-        num_exp = es.get_num_members( poly_set.get_embedded_degree() )
+        num_nodes = len(self.nodes)
+        es = poly_set.get_expansion_set()
+        num_exp = es.get_num_members(poly_set.get_embedded_degree())
 
-        riesz_shape = tuple( [ num_nodes ] + list( tshape ) + [ num_exp ] )
+        riesz_shape = tuple([num_nodes] + list(tshape) + [num_exp])
 
-        self.mat = numpy.zeros( riesz_shape, "d" )
+        self.mat = numpy.zeros(riesz_shape, "d")
 
-        for i in range( len( self.nodes ) ):
-            self.mat[i][:] = self.nodes[i].to_riesz( poly_set )
+        for i in range(len(self.nodes)):
+            self.mat[i][:] = self.nodes[i].to_riesz(poly_set)
 
         return self.mat
diff --git a/FIAT/enriched.py b/FIAT/enriched.py
new file mode 100644
index 0000000..e6d5d77
--- /dev/null
+++ b/FIAT/enriched.py
@@ -0,0 +1,174 @@
+# Copyright (C) 2013 Andrew T. T. McRae, 2015-2016 Jan Blechta, and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import numpy as np
+from copy import copy
+
+from FIAT.finite_element import FiniteElement
+from FIAT.dual_set import DualSet
+
+__all__ = ['EnrichedElement']
+
+
+class EnrichedElement(FiniteElement):
+    """Class implementing a finite element that combined the degrees of freedom
+    of two existing finite elements.
+
+    This is an implementation which does not care about orthogonality of
+    primal and dual basis.
+    """
+
+    def __init__(self, *elements):
+        assert len(elements) == 2, "EnrichedElement only implemented for two subelements"
+        A, B = elements
+
+        # Firstly, check it makes sense to enrich.  Elements must have:
+        # - same reference element
+        # - same mapping
+        # - same value shape
+        if not A.get_reference_element() == B.get_reference_element():
+            raise ValueError("Elements must be defined on the same reference element")
+        if not A.mapping()[0] == B.mapping()[0]:
+            raise ValueError("Elements must have same mapping")
+        if not A.value_shape() == B.value_shape():
+            raise ValueError("Elements must have the same value shape")
+
+        # order is at least max, possibly more, though getting this
+        # right isn't important AFAIK
+        order = max(A.get_order(), B.get_order())
+        # form degree is essentially max (not true for Hdiv/Hcurl,
+        # but this will raise an error above anyway).
+        # E.g. an H^1 function enriched with an L^2 is now just L^2.
+        if A.get_formdegree() is None or B.get_formdegree() is None:
+            formdegree = None
+        else:
+            formdegree = max(A.get_formdegree(), B.get_formdegree())
+
+        # set up reference element and mapping, following checks above
+        ref_el = A.get_reference_element()
+        mapping = A.mapping()[0]
+
+        # set up entity_ids - for each geometric entity, just concatenate
+        # the entities of the constituent elements
+        Adofs = A.entity_dofs()
+        Bdofs = B.entity_dofs()
+        offset = A.space_dimension()  # number of entities belonging to A
+        entity_ids = {}
+
+        for ent_dim in Adofs:
+            entity_ids[ent_dim] = {}
+            for ent_dim_index in Adofs[ent_dim]:
+                entlist = copy(Adofs[ent_dim][ent_dim_index])
+                entlist += [c + offset for c in Bdofs[ent_dim][ent_dim_index]]
+                entity_ids[ent_dim][ent_dim_index] = entlist
+
+        # set up dual basis - just concatenation
+        nodes = A.dual_basis() + B.dual_basis()
+        dual = DualSet(nodes, ref_el, entity_ids)
+
+        super(EnrichedElement, self).__init__(ref_el, dual, order, formdegree, mapping)
+
+        # Set up constituent elements
+        self.A = A
+        self.B = B
+
+        # required degree (for quadrature) is definitely max
+        self.polydegree = max(A.degree(), B.degree())
+
+        # Store subelements
+        self._elements = elements
+
+    def elements(self):
+        "Return reference to original subelements"
+        return self._elements
+
+    def degree(self):
+        """Return the degree of the (embedding) polynomial space."""
+        return self.polydegree
+
+    def get_nodal_basis(self):
+        """Return the nodal basis, encoded as a PolynomialSet object,
+        for the finite element."""
+        raise NotImplementedError("get_nodal_basis not implemented")
+
+    def get_coeffs(self):
+        """Return the expansion coefficients for the basis of the
+        finite element."""
+        raise NotImplementedError("get_coeffs not implemented")
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points."""
+
+        # Again, simply concatenate at the basis-function level
+        # Number of array dimensions depends on whether the space
+        # is scalar- or vector-valued, so treat these separately.
+
+        Asd = self.A.space_dimension()
+        Bsd = self.B.space_dimension()
+        Atab = self.A.tabulate(order, points, entity)
+        Btab = self.B.tabulate(order, points, entity)
+        npoints = len(points)
+        vs = self.A.value_shape()
+        rank = len(vs)  # scalar: 0, vector: 1
+
+        result = {}
+        for index in Atab:
+            if rank == 0:
+                # scalar valued
+                # Atab[index] and Btab[index] look like
+                # array[basis_fn][point]
+                # We build a new array, which will be the concatenation
+                # of the two subarrays, in the first index.
+
+                temp = np.zeros((Asd + Bsd, npoints),
+                                dtype=Atab[index].dtype)
+                temp[:Asd, :] = Atab[index][:, :]
+                temp[Asd:, :] = Btab[index][:, :]
+
+                result[index] = temp
+            elif rank == 1:
+                # vector valued
+                # Atab[index] and Btab[index] look like
+                # array[basis_fn][x/y/z][point]
+                # We build a new array, which will be the concatenation
+                # of the two subarrays, in the first index.
+
+                temp = np.zeros((Asd + Bsd, vs[0], npoints),
+                                dtype=Atab[index].dtype)
+                temp[:Asd, :, :] = Atab[index][:, :, :]
+                temp[Asd:, :, :] = Btab[index][:, :, :]
+
+                result[index] = temp
+            else:
+                raise NotImplementedError("must be scalar- or vector-valued")
+        return result
+
+    def value_shape(self):
+        """Return the value shape of the finite element functions."""
+        return self.A.value_shape()
+
+    def dmats(self):
+        """Return dmats: expansion coefficients for basis function
+        derivatives."""
+        raise NotImplementedError("dmats not implemented")
+
+    def get_num_members(self, arg):
+        """Return number of members of the expansion set."""
+        raise NotImplementedError("get_num_members not implemented")
diff --git a/FIAT/expansions.py b/FIAT/expansions.py
index 22dbbcd..b735f6f 100644
--- a/FIAT/expansions.py
+++ b/FIAT/expansions.py
@@ -14,16 +14,24 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
 """Principal orthogonal expansion functions as defined by Karniadakis
 and Sherwin.  These are parametrized over a reference element so as
 to allow users to get coordinates that they want."""
 
+from __future__ import absolute_import, print_function, division
+
 import numpy
 import math
 import sympy
-from . import reference_element
-from . import jacobi
+from FIAT import reference_element
+from FIAT import jacobi
+
+
+def jrc(a, b, n):
+    an = (2*n+1+a+b)*(2*n+2+a+b) / (2*(n+1)*(n+1+a+b))
+    bn = (a*a-b*b) * (2*n+1+a+b) / (2*(n+1)*(2*n+a+b)*(n+1+a+b))
+    cn = (n+a)*(n+b)*(2*n+2+a+b) / ((n+1)*(n+1+a+b)*(2*n+a+b))
+    return an, bn, cn
 
 
 def _tabulate_dpts(tabulator, D, n, order, pts):
@@ -63,7 +71,7 @@ def _tabulate_dpts(tabulator, D, n, order, pts):
             # component individually. This is necessary to maintain shapes
             # if evaluated with NumPy arrays.
             lmbd_tmp = sympy.lambdify(X, F)
-            lambda_x = lambda x: lmbd_tmp(x) + 0*x[0]
+            lambda_x = lambda x: lmbd_tmp(x) + 0 * x[0]
         return lambda_x
 
     def evaluate_lambda(lmbd, x):
@@ -81,8 +89,8 @@ def _tabulate_dpts(tabulator, D, n, order, pts):
     # append derivatives
     symbolic_tab = [[phi] for phi in symbolic_tab]
     #
-    data = (order+1) * [None]
-    for r in range(order+1):
+    data = (order + 1) * [None]
+    for r in range(order + 1):
         shape = [len(symbolic_tab), len(pts)] + r * [D]
         data[r] = numpy.empty(shape)
         for i, phi in enumerate(symbolic_tab):
@@ -114,8 +122,9 @@ def xi_tetrahedron(eta):
     return xi1, xi2, xi3
 
 
-class LineExpansionSet:
+class LineExpansionSet(object):
     """Evaluates the Legendre basis on a line reference element."""
+
     def __init__(self, ref_el):
         if ref_el.get_spatial_dimension() != 1:
             raise Exception("Must have a line")
@@ -128,7 +137,7 @@ class LineExpansionSet:
         self.scale = numpy.sqrt(numpy.linalg.det(self.A))
 
     def get_num_members(self, n):
-        return n+1
+        return n + 1
 
     def tabulate(self, n, pts):
         """Returns a numpy array A[i,j] = phi_i(pts[j])"""
@@ -136,7 +145,7 @@ class LineExpansionSet:
             ref_pts = numpy.array([self.mapping(pt) for pt in pts])
             psitilde_as = jacobi.eval_jacobi_batch(0, 0, n, ref_pts)
 
-            results = numpy.zeros((n+1, len(pts)), type(pts[0][0]))
+            results = numpy.zeros((n + 1, len(pts)), type(pts[0][0]))
             for k in range(n + 1):
                 results[k, :] = psitilde_as[k, :] * math.sqrt(k + 0.5)
 
@@ -153,9 +162,9 @@ class LineExpansionSet:
         psitilde_as_derivs = jacobi.eval_jacobi_deriv_batch(0, 0, n, ref_pts)
 
         # Jacobi polynomials defined on [-1, 1], first derivatives need scaling
-        psitilde_as_derivs *= 2.0/self.ref_el.volume()
+        psitilde_as_derivs *= 2.0 / self.ref_el.volume()
 
-        results = numpy.zeros((n+1, len(pts)), "d")
+        results = numpy.zeros((n + 1, len(pts)), "d")
         for k in range(0, n + 1):
             results[k, :] = psitilde_as_derivs[k, :] * numpy.sqrt(k + 0.5)
 
@@ -171,9 +180,11 @@ class LineExpansionSet:
 
         return dv
 
-class TriangleExpansionSet:
+
+class TriangleExpansionSet(object):
     """Evaluates the orthonormal Dubiner basis on a triangular
     reference element."""
+
     def __init__(self, ref_el):
         if ref_el.get_spatial_dimension() != 2:
             raise Exception("Must have a triangle")
@@ -186,7 +197,7 @@ class TriangleExpansionSet:
 #        self.scale = numpy.sqrt(numpy.linalg.det(self.A))
 
     def get_num_members(self, n):
-        return (n+1)*(n+2)//2
+        return (n + 1) * (n + 2) // 2
 
     def tabulate(self, n, pts):
         if len(pts) == 0:
@@ -199,22 +210,12 @@ class TriangleExpansionSet:
         '''
         m1, m2 = self.A.shape
         ref_pts = [sum(self.A[i][j] * pts[j] for j in range(m2)) + self.b[i]
-                   for i in range(m1)
-                   ]
+                   for i in range(m1)]
 
         def idx(p, q):
-            return (p+q)*(p+q+1)//2 + q
-
-        def jrc(a, b, n):
-            an = float((2*n+1+a+b)*(2*n+2+a+b)) \
-                / float(2*(n+1)*(n+1+a+b))
-            bn = float((a*a-b*b) * (2*n+1+a+b)) \
-                / float(2*(n+1)*(2*n+a+b)*(n+1+a+b))
-            cn = float((n+a)*(n+b)*(2*n+2+a+b)) \
-                / float((n+1)*(n+1+a+b)*(2*n+a+b))
-            return an, bn, cn
+            return (p + q) * (p + q + 1) // 2 + q
 
-        results = ((n+1)*(n+2)//2) * [None]
+        results = ((n + 1) * (n + 2) // 2) * [None]
 
         results[0] = 1.0 \
             + pts[0] - pts[0] \
@@ -226,14 +227,14 @@ class TriangleExpansionSet:
         x = ref_pts[0]
         y = ref_pts[1]
 
-        f1 = (1.0+2*x+y)/2.0
+        f1 = (1.0 + 2 * x + y) / 2.0
         f2 = (1.0 - y) / 2.0
         f3 = f2**2
 
         results[idx(1, 0)] = f1
 
         for p in range(1, n):
-            a = (2.0*p+1)/(1.0+p)
+            a = (2.0 * p + 1) / (1.0 + p)
             # b = p / (p+1.0)
             results[idx(p+1, 0)] = a * f1 * results[idx(p, 0)] \
                 - p/(1.0+p) * f3 * results[idx(p-1, 0)]
@@ -242,19 +243,19 @@ class TriangleExpansionSet:
             results[idx(p, 1)] = 0.5 * (1+2.0*p+(3.0+2.0*p)*y) \
                 * results[idx(p, 0)]
 
-        for p in range(n-1):
-            for q in range(1, n-p):
-                (a1, a2, a3) = jrc(2*p+1, 0, q)
+        for p in range(n - 1):
+            for q in range(1, n - p):
+                (a1, a2, a3) = jrc(2 * p + 1, 0, q)
                 results[idx(p, q+1)] = \
                     (a1 * y + a2) * results[idx(p, q)] \
                     - a3 * results[idx(p, q-1)]
 
-        for p in range(n+1):
-            for q in range(n-p+1):
-                results[idx(p, q)] *= math.sqrt((p+0.5)*(p+q+1.0))
+        for p in range(n + 1):
+            for q in range(n - p + 1):
+                results[idx(p, q)] *= math.sqrt((p + 0.5) * (p + q + 1.0))
 
         return results
-        #return self.scale * results
+        # return self.scale * results
 
     def tabulate_derivatives(self, n, pts):
         order = 1
@@ -266,15 +267,16 @@ class TriangleExpansionSet:
         n = data[0].shape[1]
         data2 = [[tuple([data[r][i][j] for r in range(order+1)])
                   for j in range(n)]
-                 for i in range(m)]        
+                 for i in range(m)]
         return data2
 
     def tabulate_jet(self, n, pts, order=1):
         return _tabulate_dpts(self._tabulate, 2, n, order, numpy.array(pts))
 
 
-class TetrahedronExpansionSet:
+class TetrahedronExpansionSet(object):
     """Collapsed orthonormal polynomial expanion on a tetrahedron."""
+
     def __init__(self, ref_el):
         if ref_el.get_spatial_dimension() != 3:
             raise Exception("Must be a tetrahedron")
@@ -286,10 +288,8 @@ class TetrahedronExpansionSet:
         self.mapping = lambda x: numpy.dot(self.A, x) + self.b
         self.scale = numpy.sqrt(numpy.linalg.det(self.A))
 
-        return
-
     def get_num_members(self, n):
-        return (n+1)*(n+2)*(n+3)//6
+        return (n + 1) * (n + 2) * (n + 3) // 6
 
     def tabulate(self, n, pts):
         if len(pts) == 0:
@@ -302,22 +302,12 @@ class TetrahedronExpansionSet:
         '''
         m1, m2 = self.A.shape
         ref_pts = [sum(self.A[i][j] * pts[j] for j in range(m2)) + self.b[i]
-                   for i in range(m1)
-                   ]
+                   for i in range(m1)]
 
         def idx(p, q, r):
-            return (p+q+r)*(p+q+r+1)*(p+q+r+2)//6 + (q+r)*(q+r+1)//2 + r
-
-        def jrc(a, b, n):
-            an = float((2*n+1+a+b)*(2*n+2+a+b)) \
-                / float(2*(n+1)*(n+1+a+b))
-            bn = float((a*a-b*b) * (2*n+1+a+b)) \
-                / float(2*(n+1)*(2*n+a+b)*(n+1+a+b))
-            cn = float((n+a)*(n+b)*(2*n+2+a+b)) \
-                / float((n+1)*(n+1+a+b)*(2*n+a+b))
-            return an, bn, cn
-
-        results = ((n+1)*(n+2)*(n+3)//6) * [None]
+            return (p + q + r)*(p + q + r + 1)*(p + q + r + 2)//6 + (q + r)*(q + r + 1)//2 + r
+
+        results = ((n + 1) * (n + 2) * (n + 3) // 6) * [None]
         results[0] = 1.0 \
             + pts[0] - pts[0] \
             + pts[1] - pts[1] \
@@ -330,11 +320,11 @@ class TetrahedronExpansionSet:
         y = ref_pts[1]
         z = ref_pts[2]
 
-        factor1 = 0.5 * (2.0 + 2.0*x + y + z)
-        factor2 = (0.5*(y+z))**2
+        factor1 = 0.5 * (2.0 + 2.0 * x + y + z)
+        factor2 = (0.5 * (y + z))**2
         factor3 = 0.5 * (1 + 2.0 * y + z)
         factor4 = 0.5 * (1 - z)
-        factor5 = factor4 ** 2
+        factor5 = factor4**2
 
         results[idx(1, 0, 0)] = factor1
         for p in range(1, n):
@@ -348,9 +338,9 @@ class TetrahedronExpansionSet:
             results[idx(p, 1, 0)] = results[idx(p, 0, 0)] \
                 * (p * (1.0 + y) + (2.0 + 3.0 * y + z) / 2)
 
-        for p in range(0, n-1):
-            for q in range(1, n-p):
-                (aq, bq, cq) = jrc(2*p+1, 0, q)
+        for p in range(0, n - 1):
+            for q in range(1, n - p):
+                (aq, bq, cq) = jrc(2 * p + 1, 0, q)
                 qmcoeff = aq * factor3 + bq * factor4
                 qm1coeff = cq * factor5
                 results[idx(p, q+1, 0)] = qmcoeff * results[idx(p, q, 0)] \
@@ -358,22 +348,22 @@ class TetrahedronExpansionSet:
 
         # now handle r=1
         for p in range(n):
-            for q in range(n-p):
+            for q in range(n - p):
                 results[idx(p, q, 1)] = results[idx(p, q, 0)] \
                     * (1.0 + p + q + (2.0 + q + p) * z)
 
         # general r by recurrence
-        for p in range(n-1):
-            for q in range(0, n-p-1):
-                for r in range(1, n-p-q):
-                    ar, br, cr = jrc(2*p+2*q+2, 0, r)
+        for p in range(n - 1):
+            for q in range(0, n - p - 1):
+                for r in range(1, n - p - q):
+                    ar, br, cr = jrc(2 * p + 2 * q + 2, 0, r)
                     results[idx(p, q, r+1)] = \
-                                (ar * z + br) * results[idx(p, q, r) ] \
-                                - cr * results[idx(p, q, r-1) ]
+                        (ar * z + br) * results[idx(p, q, r)] \
+                        - cr * results[idx(p, q, r-1)]
 
-        for p in range(n+1):
-            for q in range(n-p+1):
-                for r in range(n-p-q+1):
+        for p in range(n + 1):
+            for q in range(n - p + 1):
+                for r in range(n - p - q + 1):
                     results[idx(p, q, r)] *= \
                         math.sqrt((p+0.5)*(p+q+1.0)*(p+q+r+1.5))
 
@@ -388,7 +378,7 @@ class TetrahedronExpansionSet:
         # (gradient, Hessian, ...)
         m = data[0].shape[0]
         n = data[0].shape[1]
-        data2 = [[tuple([data[r][i][j] for r in range(order+1)])
+        data2 = [[tuple([data[r][i][j] for r in range(order + 1)])
                   for j in range(n)]
                  for i in range(m)]
         return data2
@@ -397,7 +387,7 @@ class TetrahedronExpansionSet:
         return _tabulate_dpts(self._tabulate, 3, n, order, numpy.array(pts))
 
 
-def get_expansion_set( ref_el ):
+def get_expansion_set(ref_el):
     """Returns an ExpansionSet instance appopriate for the given
     reference element."""
     if ref_el.get_shape() == reference_element.LINE:
@@ -416,34 +406,8 @@ def polynomial_dimension(ref_el, degree):
     if ref_el.get_shape() == reference_element.LINE:
         return max(0, degree + 1)
     elif ref_el.get_shape() == reference_element.TRIANGLE:
-        return max((degree+1)*(degree+2)//2, 0)
+        return max((degree + 1) * (degree + 2) // 2, 0)
     elif ref_el.get_shape() == reference_element.TETRAHEDRON:
-        return max(0, (degree+1)*(degree+2)*(degree+3)//6)
+        return max(0, (degree + 1) * (degree + 2) * (degree + 3) // 6)
     else:
         raise Exception("Unknown reference element type.")
-
-
-if __name__ == "__main__":
-    from . import expansions
-
-    E = reference_element.DefaultTriangle()
-
-    k = 3
-
-    pts = E.make_lattice(k)
-
-    Phis = expansions.get_expansion_set(E)
-
-    phis = Phis.tabulate(k, pts)    
-
-    dphis = Phis.tabulate_derivatives(k, pts)
-
-#    dphis_x = numpy.array([[d[1][0] for d in dphi] for dphi in dphis])
-#    dphis_y = numpy.array([[d[1][1] for d in dphi] for dphi in dphis])
-#    dphis_z = numpy.array([[d[1][2] for d in dphi] for dphi in dphis])
-
-#    print dphis_x
-
-#    for dmat in make_dmats(E, k):
-#        print dmat
-#        print
diff --git a/FIAT/finite_element.py b/FIAT/finite_element.py
index ae6dc0b..8e0d926 100644
--- a/FIAT/finite_element.py
+++ b/FIAT/finite_element.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -14,118 +15,180 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Modified by David A. Ham (david.ham at imperial.ac.uk), 2014
+# Modified by Thomas H. Gibson (t.gibson15 at imperial.ac.uk), 2016
+
+from __future__ import absolute_import, print_function, division
 
 import numpy
-from .polynomial_set import PolynomialSet
+from six.moves import map
 
-class FiniteElement:
-    """Class implementing Ciarlet's abstraction of a finite element
-    being a domain, function space, and set of nodes."""
-    def __init__( self , poly_set , dual , order, mapping="affine"):
-        # first, compare ref_el of poly_set and dual
-        # need to overload equality
-        #if poly_set.get_reference_element() != dual.get_reference_element:
-        #    raise Exception, ""
+from FIAT.polynomial_set import PolynomialSet
+from FIAT.quadrature_schemes import create_quadrature
 
+
+class FiniteElement(object):
+    """Class implementing a basic abstraction template for general
+    finite element families. Finite elements which inherit from
+    this class are non-nodal unless they are CiarletElement subclasses.
+    """
+
+    def __init__(self, ref_el, dual, order, formdegree=None, mapping="affine"):
+        # Relevant attributes that do not necessarily depend on a PolynomialSet object:
         # The order (degree) of the polynomial basis
         self.order = order
+        self.formdegree = formdegree
 
-        self.ref_el = poly_set.get_reference_element()
+        # The reference element and the appropriate dual
+        self.ref_el = ref_el
         self.dual = dual
 
-        # Appropriate mapping for the element space
+        # The appropriate mapping for the finite element space
         self._mapping = mapping
 
+    def get_reference_element(self):
+        """Return the reference element for the finite element."""
+        return self.ref_el
+
+    def get_dual_set(self):
+        """Return the dual for the finite element."""
+        return self.dual
+
+    def get_order(self):
+        """Return the order of the element (may be different from the degree."""
+        return self.order
+
+    def dual_basis(self):
+        """Return the dual basis (list of functionals) for the finite
+        element."""
+        return self.dual.get_nodes()
+
+    def entity_dofs(self):
+        """Return the map of topological entities to degrees of
+        freedom for the finite element."""
+        return self.dual.get_entity_ids()
+
+    def entity_closure_dofs(self):
+        """Return the map of topological entities to degrees of
+        freedom on the closure of those entities for the finite element."""
+        return self.dual.get_entity_closure_ids()
+
+    def get_formdegree(self):
+        """Return the degree of the associated form (FEEC)"""
+        return self.formdegree
+
+    def mapping(self):
+        """Return a list of appropriate mappings from the reference
+        element to a physical element for each basis function of the
+        finite element."""
+        return [self._mapping] * self.space_dimension()
+
+    def num_sub_elements(self):
+        """Return the number of sub-elements."""
+        return 1
+
+    def space_dimension(self):
+        """Return the dimension of the finite element space."""
+        return len(self.dual_basis())
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points.
+
+        :arg order: The maximum order of derivative.
+        :arg points: An iterable of points.
+        :arg entity: Optional (dimension, entity number) pair
+                     indicating which topological entity of the
+                     reference element to tabulate on.  If ``None``,
+                     default cell-wise tabulation is performed.
+        """
+        raise NotImplementedError("Must be specified in the element subclass of FiniteElement.")
+
+    @staticmethod
+    def is_nodal():
+        """True if primal and dual bases are orthogonal. If false,
+        dual basis is not implemented or is undefined.
+
+        Subclasses may not necessarily be nodal, unless it is a CiarletElement.
+        """
+        return False
+
+
+class CiarletElement(FiniteElement):
+    """Class implementing Ciarlet's abstraction of a finite element
+    being a domain, function space, and set of nodes.
+
+    Elements derived from this class are nodal finite elements, with a nodal
+    basis generated from polynomials encoded in a `PolynomialSet`.
+    """
+
+    def __init__(self, poly_set, dual, order, formdegree=None, mapping="affine"):
+        ref_el = poly_set.get_reference_element()
+        super(CiarletElement, self).__init__(ref_el, dual, order, formdegree, mapping)
+
         # build generalized Vandermonde matrix
         old_coeffs = poly_set.get_coeffs()
-        dualmat = dual.to_riesz( poly_set )
+        dualmat = dual.to_riesz(poly_set)
 
         shp = dualmat.shape
-        if len( shp ) > 2:
-            num_cols = numpy.prod( shp[1:] )
+        if len(shp) > 2:
+            num_cols = numpy.prod(shp[1:])
 
-            A = numpy.reshape( dualmat, (dualmat.shape[0], num_cols) )
-            B = numpy.reshape( old_coeffs, (old_coeffs.shape[0], num_cols ) )
+            A = numpy.reshape(dualmat, (dualmat.shape[0], num_cols))
+            B = numpy.reshape(old_coeffs, (old_coeffs.shape[0], num_cols))
         else:
             A = dualmat
             B = old_coeffs
 
-        V = numpy.dot( A, numpy.transpose( B ) )
-        self.V=V
-        (u, s, vt) = numpy.linalg.svd( V )
-
-        Vinv = numpy.linalg.inv( V )
+        V = numpy.dot(A, numpy.transpose(B))
+        self.V = V
 
-        new_coeffs_flat = numpy.dot( numpy.transpose( Vinv ), B)
+        Vinv = numpy.linalg.inv(V)
 
-        new_shp = tuple( [ new_coeffs_flat.shape[0] ] \
-                          + list( shp[1:] ) )
-        new_coeffs = numpy.reshape( new_coeffs_flat, \
-                                    new_shp )
+        new_coeffs_flat = numpy.dot(numpy.transpose(Vinv), B)
 
-        self.poly_set = PolynomialSet( self.ref_el, \
-                                       poly_set.get_degree(), \
-                                       poly_set.get_embedded_degree(), \
-                                       poly_set.get_expansion_set(), \
-                                       new_coeffs, \
-                                       poly_set.get_dmats() )
+        new_shp = tuple([new_coeffs_flat.shape[0]] + list(shp[1:]))
+        new_coeffs = numpy.reshape(new_coeffs_flat, new_shp)
 
-        return
+        self.poly_set = PolynomialSet(ref_el,
+                                      poly_set.get_degree(),
+                                      poly_set.get_embedded_degree(),
+                                      poly_set.get_expansion_set(),
+                                      new_coeffs,
+                                      poly_set.get_dmats())
 
     def degree(self):
         "Return the degree of the (embedding) polynomial space."
         return self.poly_set.get_embedded_degree()
 
-    def get_reference_element( self ):
-        "Return the reference element for the finite element."
-        return self.ref_el
-
-    def get_nodal_basis( self ):
+    def get_nodal_basis(self):
         """Return the nodal basis, encoded as a PolynomialSet object,
         for the finite element."""
         return self.poly_set
 
-    def get_dual_set( self ):
-        "Return the dual for the finite element."
-        return self.dual
-
-    def get_order( self ):
-        "Return the order of the element (may be different from the degree)"
-        return self.order
-
-    def dual_basis(self):
-        """Return the dual basis (list of functionals) for the finite
-        element."""
-        return self.dual.get_nodes()
-
-    def entity_dofs(self):
-        """Return the map of topological entities to degrees of
-        freedom for the finite element."""
-        return self.dual.get_entity_ids()
-
     def get_coeffs(self):
         """Return the expansion coefficients for the basis of the
         finite element."""
         return self.poly_set.get_coeffs()
 
-    def mapping(self):
-        """Return a list of appropriate mappings from the reference
-        element to a physical element for each basis function of the
-        finite element."""
-        return [self._mapping]*self.space_dimension()
-
-    def num_sub_elements(self):
-        "Return the number of sub-elements."
-        return 1
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points.
 
-    def space_dimension(self):
-        "Return the dimension of the finite element space."
-        return self.poly_set.get_num_members()
+        :arg order: The maximum order of derivative.
+        :arg points: An iterable of points.
+        :arg entity: Optional (dimension, entity number) pair
+                     indicating which topological entity of the
+                     reference element to tabulate on.  If ``None``,
+                     default cell-wise tabulation is performed.
+        """
+        if entity is None:
+            entity = (self.ref_el.get_spatial_dimension(), 0)
 
-    def tabulate(self, order, points):
-        """Return tabulated values of derivatives up to given order of
-        basis functions at given points."""
-        return self.poly_set.tabulate(points, order)
+        entity_dim, entity_id = entity
+        transform = self.ref_el.get_entity_transform(entity_dim, entity_id)
+        return self.poly_set.tabulate(list(map(transform, points)), order)
 
     def value_shape(self):
         "Return the value shape of the finite element functions."
@@ -139,3 +202,57 @@ class FiniteElement:
     def get_num_members(self, arg):
         "Return number of members of the expansion set."
         return self.get_nodal_basis().get_expansion_set().get_num_members(arg)
+
+    @staticmethod
+    def is_nodal():
+        """True if primal and dual bases are orthogonal. If false,
+        dual basis is not implemented or is undefined.
+
+        All implementations/subclasses are nodal including this one.
+        """
+        return True
+
+
+def entity_support_dofs(elem, entity_dim):
+    """Return the map of entity id to the degrees of freedom for which the
+    corresponding basis functions take non-zero values
+
+    :arg elem: FIAT finite element
+    :arg entity_dim: Dimension of the cell subentity.
+    """
+    if not hasattr(elem, "_entity_support_dofs"):
+        elem._entity_support_dofs = {}
+    cache = elem._entity_support_dofs
+    try:
+        return cache[entity_dim]
+    except KeyError:
+        pass
+
+    ref_el = elem.get_reference_element()
+    dim = ref_el.get_spatial_dimension()
+
+    entity_cell = ref_el.construct_subelement(entity_dim)
+    quad = create_quadrature(entity_cell, max(2*elem.degree(), 1))
+    weights = quad.get_weights()
+
+    eps = 1.e-8  # Is this a safe value?
+
+    result = {}
+    for f in elem.entity_dofs()[entity_dim].keys():
+        entity_transform = ref_el.get_entity_transform(entity_dim, f)
+        points = list(map(entity_transform, quad.get_points()))
+
+        # Integrate the square of the basis functions on the facet.
+        vals = numpy.double(elem.tabulate(0, points)[(0,) * dim])
+        # Ints contains the square of the basis functions
+        # integrated over the facet.
+        if elem.value_shape():
+            # Vector-valued functions.
+            ints = numpy.dot(numpy.einsum("...ij,...ij->...j", vals, vals), weights)
+        else:
+            ints = numpy.dot(vals**2, weights)
+
+        result[f] = [dof for dof, i in enumerate(ints) if i > eps]
+
+    cache[entity_dim] = result
+    return result
diff --git a/FIAT/functional.py b/FIAT/functional.py
index c50a8b7..fa4551b 100644
--- a/FIAT/functional.py
+++ b/FIAT/functional.py
@@ -21,9 +21,12 @@
 # - a reference element domain
 # - type information
 
-import numpy
-from functools import reduce
+from __future__ import absolute_import, print_function, division
+
 from collections import OrderedDict
+from itertools import chain
+import numpy
+import sympy
 
 
 def index_iterator(shp):
@@ -47,62 +50,36 @@ def index_iterator(shp):
 # integers
 
 
-class Functional:
+class Functional(object):
     """Class implementing an abstract functional.
     All functionals are discrete in the sense that
     the are written as a weighted sum of (components of) their
     argument evaluated at particular points."""
-    def __init__(self, ref_el, target_shape,
-                 pt_dict, deriv_dict, functional_type
-                 ):
+
+    def __init__(self, ref_el, target_shape, pt_dict, deriv_dict, functional_type):
         self.ref_el = ref_el
         self.target_shape = target_shape
         self.pt_dict = pt_dict
         self.deriv_dict = deriv_dict
         self.functional_type = functional_type
         if len(deriv_dict) > 0:
-            per_point = reduce(lambda a, b: a + b, list(deriv_dict.values()))
-            alphas = \
-                [foo[1] for foo in per_point]
+            per_point = list(chain(*deriv_dict.values()))
+            alphas = [foo[1] for foo in per_point]
             self.max_deriv_order = max([sum(foo) for foo in alphas])
         else:
             self.max_deriv_order = 0
-        return
 
     def evaluate(self, f):
-        """Evaluates the functional on some callable object f."""
-        result = 0
-
-        # non-derivative part
-        # TODO pt_dict? comp?
-        for pt in pt_dict:
-            wc_list = pt_dict[pt]
-            for (w, c) in wc_list:
-                if comp == tuple:
-                    result += w * f(pt)
-                else:
-                    result += w * f(pt)[comp]
-
-        # Import AD modules from ScientificPython
-        # import Scientific.Functions.Derivatives as Derivatives
-        for pt in self.deriv_dict:
-            dpt = tuple([Derivatives.DerivVar(pt[i], i, self.max_deriv_order)
-                         for i in range(len(pt))
-                         ])
-            for (w, a, c) in self.deriv_dict[pt]:
-                fpt = f(dpt)
-                order = sum(a)
-                if c == tuple():
-                    val_cur = fpt[order]
-                else:
-                    val_cur = fpt[c][order]
-                for i in range(len[a]):
-                    for j in range(a[j]):
-                        val_cur = val_cur[i]
-
-                result += val_cur
+        """Obsolete and broken functional evaluation.
 
-        return result
+        To evaluate the functional, call it on the target function:
+
+          functional(function)
+        """
+        raise AttributeError("To evaluate the functional just call it on a function.")
+
+    def __call__(self, fn):
+        raise NotImplementedError("Evaluation is not yet implemented for %s" % type(self))
 
     def get_point_dict(self):
         """Returns the functional information, which is a dictionary
@@ -137,8 +114,6 @@ class Functional:
 
         result = numpy.zeros(poly_set.coeffs.shape[1:], "d")
 
-        shp = poly_set.get_shape()
-
         # loop over points
         for j in range(len(pts)):
             pt_cur = pts[j]
@@ -149,31 +124,8 @@ class Functional:
                 for (w, c) in wc_list:
                     result[c][i] += w * bfs[i, j]
 
-        def pt_to_dpt(pt, dorder):
-            dpt = []
-            for i in range(len(pt)):
-                dpt.append(Derivatives.DerivVar(pt[i], i, dorder))
-            return tuple(dpt)
-
-        # loop over deriv points
-        dpt_dict = self.deriv_dict
-        mdo = self.max_deriv_order
-
-        dpts = list(dpt_dict.keys())
-        dpts_dv = [pt_to_dpt(pt, mdo) for pt in dpts]
-
-        dbfs = es.tabulate(ed, dpts_dv)
-
-        for j in range(len(dpts)):
-            dpt_cur = dpts[j]
-            for i in range(dbfs.shape[0]):
-                for (w, a, c) in dpt_dict[dpt_cur]:
-                    dval_cur = dbfs[i, j][sum(a)]
-                    for k in range(len(a)):
-                        for l in range(a[k]):
-                            dval_cur = dval_cur[k]
-
-                    result[c][i] += w * dval_cur
+        if self.deriv_dict:
+            raise NotImplementedError("Generic to_riesz implementation does not support derivatives")
 
         return result
 
@@ -184,11 +136,14 @@ class Functional:
 class PointEvaluation(Functional):
     """Class representing point evaluation of scalar functions at a
     particular point x."""
+
     def __init__(self, ref_el, x):
         pt_dict = {x: [(1.0, tuple())]}
-        Functional.__init__(self, ref_el, tuple(), pt_dict, {},
-                            "PointEval")
-        return
+        Functional.__init__(self, ref_el, tuple(), pt_dict, {}, "PointEval")
+
+    def __call__(self, fn):
+        """Evaluate the functional on the function fn."""
+        return fn(tuple(self.pt_dict.keys())[0])
 
     def tostr(self):
         x = list(map(str, list(self.pt_dict.keys())[0]))
@@ -198,6 +153,7 @@ class PointEvaluation(Functional):
 class ComponentPointEvaluation(Functional):
     """Class representing point evaluation of a particular component
     of a vector function at a particular point x."""
+
     def __init__(self, ref_el, comp, shp, x):
         if len(shp) != 1:
             raise Exception("Illegal shape")
@@ -216,37 +172,49 @@ class ComponentPointEvaluation(Functional):
 class PointDerivative(Functional):
     """Class representing point partial differentiation of scalar
     functions at a particular point x."""
+
     def __init__(self, ref_el, x, alpha):
         dpt_dict = {x: [(1.0, alpha, tuple())]}
         self.alpha = alpha
         self.order = sum(self.alpha)
 
-        Functional.__init__(self, ref_el, tuple(), {},
-                            dpt_dict, "PointDeriv"
-                            )
-        return
+        Functional.__init__(self, ref_el, tuple(), {}, dpt_dict, "PointDeriv")
+
+    def __call__(self, fn):
+        """Evaluate the functional on the function fn. Note that this depends
+        on sympy being able to differentiate fn."""
+        x = list(self.deriv_dict.keys())[0]
+
+        X = sympy.DeferredVector('x')
+        dX = numpy.asarray([X[i] for i in range(len(x))])
+
+        dvars = tuple(d for d, a in zip(dX, self.alpha)
+                      for count in range(a))
+
+        return sympy.diff(fn(X), *dvars).evalf(subs=dict(zip(dX, x)))
 
     def to_riesz(self, poly_set):
         x = list(self.deriv_dict.keys())[0]
-        dx = tuple([Derivatives.DerivVar(x[i], i, self.order)
-                    for i in range(len(x))
-                    ])
+
+        X = sympy.DeferredVector('x')
+        dx = numpy.asarray([X[i] for i in range(len(x))])
 
         es = poly_set.get_expansion_set()
         ed = poly_set.get_embedded_degree()
 
         bfs = es.tabulate(ed, [dx])[:, 0]
 
-        idx = []
-        for i in range(len(self.alpha)):
-            for j in range(self.alpha[i]):
-                idx.append(i)
-        idx = tuple(idx)
+        # Expand the multi-index as a series of variables to
+        # differentiate with respect to.
+        dvars = tuple(d for d, a in zip(dx, self.alpha)
+                      for count in range(a))
 
-        return numpy.array([numpy.array(b[self.order])[idx] for b in bfs])
+        return numpy.asarray([sympy.lambdify(X, sympy.diff(b, *dvars))(x)
+                              for b in bfs])
 
 
 class PointNormalDerivative(Functional):
+
     def __init__(self, ref_el, facet_no, pt):
         n = ref_el.compute_normal(facet_no)
         self.n = n
@@ -254,41 +222,18 @@ class PointNormalDerivative(Functional):
 
         alphas = []
         for i in range(sd):
-            alpha = [0]*sd
+            alpha = [0] * sd
             alpha[i] = 1
             alphas.append(alpha)
         dpt_dict = {pt: [(n[i], alphas[i], tuple()) for i in range(sd)]}
 
-        Functional.__init__(self, ref_el, tuple(), {},
-                            dpt_dict, "PointNormalDeriv"
-                            )
-
-        return
-
-    def to_riesz(self, poly_set):
-        #import Scientific.Functions.FirstDerivatives as FirstDerivatives
-        x = list(self.deriv_dict.keys())[0]
-        dx = tuple([FirstDerivatives.DerivVar(x[i], i)
-                    for i in range(len(x))
-                    ])
-
-        es = poly_set.get_expansion_set()
-        ed = poly_set.get_embedded_degree()
-
-        bfs = es.tabulate(ed, [dx])[:, 0]
+        Functional.__init__(self, ref_el, tuple(), {}, dpt_dict, "PointNormalDeriv")
 
-        bfs_grad = numpy.array([b[1] for b in bfs])
-        return numpy.dot(bfs_grad, self.n)
 
+class IntegralMoment(Functional):
+    """An IntegralMoment is a functional"""
 
-class IntegralMoment (Functional):
-    """
-    An IntegralMoment is a functional
-
-    """
-    def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(),
-                 shp=tuple()
-                 ):
+    def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()):
         """
         Create IntegralMoment
 
@@ -311,24 +256,36 @@ class IntegralMoment (Functional):
         for i in range(len(qpts)):
             pt_cur = tuple(qpts[i])
             pt_dict[pt_cur] = [(qwts[i] * f_at_qpts[i], comp)]
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "IntegralMoment"
-                            )
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "IntegralMoment")
+
+    def __call__(self, fn):
+        """Evaluate the functional on the function fn."""
+        pts = list(self.pt_dict.keys())
+        wts = numpy.array([foo[0][0] for foo in list(self.pt_dict.values())])
+        result = numpy.dot([fn(p) for p in pts], wts)
+
+        if self.comp:
+            result = result[self.comp]
+        return result
 
     def to_riesz(self, poly_set):
-        T = poly_set.get_reference_element()
-        sd = T.get_spatial_dimension()
         es = poly_set.get_expansion_set()
         ed = poly_set.get_embedded_degree()
         pts = list(self.pt_dict.keys())
         bfs = es.tabulate(ed, pts)
         wts = numpy.array([foo[0][0] for foo in list(self.pt_dict.values())])
         result = numpy.zeros(poly_set.coeffs.shape[1:], "d")
-        result[self.comp, :] = numpy.dot(bfs, wts)
+
+        if len(self.comp) == 0:
+            result[:] = numpy.dot(bfs, wts)
+        else:
+            result[self.comp, :] = numpy.dot(bfs, wts)
+
         return result
 
 
 class FrobeniusIntegralMoment(Functional):
+
     def __init__(self, ref_el, Q, f_at_qpts):
         # f_at_qpts is num components x num_qpts
         if len(Q.get_points()) != f_at_qpts.shape[1]:
@@ -344,9 +301,7 @@ class FrobeniusIntegralMoment(Functional):
             pt_dict[pt_cur] = [(qwts[i] * f_at_qpts[j, i], (j,))
                                for j in range(f_at_qpts.shape[0])]
 
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "FrobeniusIntegralMoment"
-                            )
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment")
 
 
 # point normals happen on a d-1 dimensional facet
@@ -354,6 +309,7 @@ class FrobeniusIntegralMoment(Functional):
 class PointNormalEvaluation(Functional):
     """Implements the evaluation of the normal component of a vector at a
     point on a facet of codimension 1."""
+
     def __init__(self, ref_el, facet_no, pt):
         n = ref_el.compute_normal(facet_no)
         self.n = n
@@ -362,24 +318,20 @@ class PointNormalEvaluation(Functional):
         pt_dict = {pt: [(n[i], (i,)) for i in range(sd)]}
 
         shp = (sd,)
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "PointNormalEval"
-                            )
-        return
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "PointNormalEval")
 
 
 class PointEdgeTangentEvaluation(Functional):
     """Implements the evaluation of the tangential component of a
     vector at a point on a facet of dimension 1."""
+
     def __init__(self, ref_el, edge_no, pt):
         t = ref_el.compute_edge_tangent(edge_no)
         self.t = t
         sd = ref_el.get_spatial_dimension()
         pt_dict = {pt: [(t[i], (i,)) for i in range(sd)]}
         shp = (sd,)
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "PointEdgeTangent"
-                            )
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "PointEdgeTangent")
 
     def tostr(self):
         x = list(map(str, list(self.pt_dict.keys())[0]))
@@ -395,6 +347,7 @@ class PointEdgeTangentEvaluation(Functional):
 class PointFaceTangentEvaluation(Functional):
     """Implements the evaluation of a tangential component of a
     vector at a point on a facet of codimension 1."""
+
     def __init__(self, ref_el, face_no, tno, pt):
         t = ref_el.compute_face_tangents(face_no)[tno]
         self.t = t
@@ -402,9 +355,7 @@ class PointFaceTangentEvaluation(Functional):
         sd = ref_el.get_spatial_dimension()
         pt_dict = {pt: [(t[i], (i,)) for i in range(sd)]}
         shp = (sd,)
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "PointFaceTangent"
-                            )
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "PointFaceTangent")
 
     def tostr(self):
         x = list(map(str, list(self.pt_dict.keys())[0]))
@@ -420,16 +371,14 @@ class PointScaledNormalEvaluation(Functional):
     """Implements the evaluation of the normal component of a vector at a
     point on a facet of codimension 1, where the normal is scaled by
     the volume of that facet."""
+
     def __init__(self, ref_el, facet_no, pt):
         self.n = ref_el.compute_scaled_normal(facet_no)
         sd = ref_el.get_spatial_dimension()
         shp = (sd,)
 
         pt_dict = {pt: [(self.n[i], (i,)) for i in range(sd)]}
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "PointScaledNormalEval"
-                            )
-        return
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "PointScaledNormalEval")
 
     def tostr(self):
         x = list(map(str, list(self.pt_dict.keys())[0]))
@@ -440,6 +389,7 @@ class PointScaledNormalEvaluation(Functional):
         phis = poly_set.get_expansion_set().tabulate(poly_set.get_embedded_degree(), xs)
         return numpy.outer(self.n, phis)
 
+
 class PointwiseInnerProductEvaluation(Functional):
     """
     This is a functional on symmetric 2-tensor fields. Let u be such a
@@ -447,46 +397,17 @@ class PointwiseInnerProductEvaluation(Functional):
     v^T u(p) w.
 
     Clearly v^iu_{ij}w^j = u_{ij}v^iw^j. Thus the value can be computed
-    from the Frobenius inner product of u with wv^T. This gives the 
+    from the Frobenius inner product of u with wv^T. This gives the
     correct weights.
     """
+
     def __init__(self, ref_el, v, w, p):
         sd = ref_el.get_spatial_dimension()
 
         wvT = numpy.outer(w, v)
-        
-        pt_dict = {p: [(wvT[i][j], (i, j, )) for [i, j] in
-                        index_iterator((sd, sd))]}
-
-        shp = (sd, sd, )
-        Functional.__init__(self, ref_el, shp,
-                            pt_dict, {}, "PointwiseInnerProductEval"
-                            )
-        return
-
-def moments_against_set(ref_el, U, Q):
-    # check that U and Q are both over ref_el
-
-    qpts = Q.get_points()
-    qwts = Q.get_weights()
-
-    Uvals = U.tabulate(pts)
-
-    # handle scalar case
-
-    for i in range(Uvals.shape[0]):  # loop over members of U
-        pass
-
-
-if __name__ == "__main__":
-    # test functionals
-    from . import polynomial_set, reference_element
-    ref_el = reference_element.DefaultTriangle()
-    sd = ref_el.get_spatial_dimension()
-    U = polynomial_set.ONPolynomialSet(ref_el, 5)
 
-    f = PointDerivative(ref_el, (0.0, 0.0), (1, 0))
-    print(numpy.allclose(Functional.to_riesz(f, U), f.to_riesz(U)))
+        pt_dict = {p: [(wvT[i][j], (i, j))
+                       for i, j in index_iterator((sd, sd))]}
 
-    f = PointNormalDerivative(ref_el, 0, (0.0, 0.0))
-    print(numpy.allclose(Functional.to_riesz(f, U), f.to_riesz(U)))
+        shp = (sd, sd)
+        Functional.__init__(self, ref_el, shp, pt_dict, {}, "PointwiseInnerProductEval")
diff --git a/FIAT/gauss_legendre.py b/FIAT/gauss_legendre.py
new file mode 100644
index 0000000..d407ba7
--- /dev/null
+++ b/FIAT/gauss_legendre.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2015 Imperial College London and others.
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Written by David A. Ham (david.ham at imperial.ac.uk), 2015
+
+from __future__ import absolute_import, print_function, division
+from FIAT import finite_element, polynomial_set, dual_set, functional, quadrature
+from FIAT.reference_element import LINE
+
+
+class GaussLegendreDualSet(dual_set.DualSet):
+    """The dual basis for 1D discontinuous elements with nodes at the
+    Gauss-Legendre points."""
+    def __init__(self, ref_el, degree):
+        entity_ids = {0: {0: [], 1: []},
+                      1: {0: list(range(0, degree+1))}}
+        l = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1)
+        nodes = [functional.PointEvaluation(ref_el, x) for x in l.pts]
+
+        super(GaussLegendreDualSet, self).__init__(nodes, ref_el, entity_ids)
+
+
+class GaussLegendre(finite_element.CiarletElement):
+    """1D discontinuous element with nodes at the Gauss-Legendre points."""
+    def __init__(self, ref_el, degree):
+        if ref_el.shape != LINE:
+            raise ValueError("Gauss-Legendre elements are only defined in one dimension.")
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = GaussLegendreDualSet(ref_el, degree)
+        super(GaussLegendre, self).__init__(poly_set, dual, degree)
diff --git a/FIAT/gauss_lobatto_legendre.py b/FIAT/gauss_lobatto_legendre.py
new file mode 100644
index 0000000..488d1c3
--- /dev/null
+++ b/FIAT/gauss_lobatto_legendre.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2015 Imperial College London and others.
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Written by David A. Ham (david.ham at imperial.ac.uk), 2015
+
+from __future__ import absolute_import, print_function, division
+
+from FIAT import finite_element, polynomial_set, dual_set, functional, quadrature
+from FIAT.reference_element import LINE
+
+
+class GaussLobattoLegendreDualSet(dual_set.DualSet):
+    """The dual basis for 1D continuous elements with nodes at the
+    Gauss-Lobatto points."""
+    def __init__(self, ref_el, degree):
+        entity_ids = {0: {0: [0], 1: [degree]},
+                      1: {0: list(range(1, degree))}}
+        l = quadrature.GaussLobattoLegendreQuadratureLineRule(ref_el, degree+1)
+        nodes = [functional.PointEvaluation(ref_el, x) for x in l.pts]
+
+        super(GaussLobattoLegendreDualSet, self).__init__(nodes, ref_el, entity_ids)
+
+
+class GaussLobattoLegendre(finite_element.CiarletElement):
+    """1D continuous element with nodes at the Gauss-Lobatto points."""
+    def __init__(self, ref_el, degree):
+        if ref_el.shape != LINE:
+            raise ValueError("Gauss-Lobatto-Legendre elements are only defined in one dimension.")
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = GaussLobattoLegendreDualSet(ref_el, degree)
+        super(GaussLobattoLegendre, self).__init__(poly_set, dual, degree)
diff --git a/FIAT/hdiv_trace.py b/FIAT/hdiv_trace.py
new file mode 100644
index 0000000..3cd143a
--- /dev/null
+++ b/FIAT/hdiv_trace.py
@@ -0,0 +1,285 @@
+# Copyright (C) 2016 Thomas H. Gibson
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import numpy as np
+from FIAT.discontinuous_lagrange import DiscontinuousLagrange
+from FIAT.reference_element import ufc_simplex
+from FIAT.functional import PointEvaluation
+from FIAT.polynomial_set import mis
+from FIAT import FiniteElement
+from FIAT import dual_set
+
+# Numerical tolerance for facet-entity identifications
+epsilon = 1e-10
+
+
+class TraceError(Exception):
+    """Exception caused by tabulating a trace element on the interior of a cell,
+    or the gradient of a trace element."""
+
+    def __init__(self, msg):
+        super(TraceError, self).__init__(msg)
+        self.msg = msg
+
+
+class HDivTrace(FiniteElement):
+    """Class implementing the trace of hdiv elements. This class
+    is a stand-alone element family that produces a DG-facet field.
+    This element is what's produced after performing the trace
+    operation on an existing H(Div) element.
+
+    This element is also known as the discontinuous trace field that
+    arises in several DG formulations.
+    """
+
+    def __init__(self, ref_el, degree):
+        sd = ref_el.get_spatial_dimension()
+        if sd in (0, 1):
+            raise ValueError("Cannot use this trace class on a %d-dimensional cell." % sd)
+
+        # Constructing facet element as a discontinuous Lagrange element
+        dglagrange = DiscontinuousLagrange(ufc_simplex(sd - 1), degree)
+
+        # Construct entity ids (assigning top. dim. and initializing as empty)
+        entity_dofs = {}
+
+        # Looping over dictionary of cell topology to construct the empty
+        # dictionary for entity ids of the trace element
+        topology = ref_el.get_topology()
+        for top_dim, entities in topology.items():
+            entity_dofs[top_dim] = {}
+            for entity in entities:
+                entity_dofs[top_dim][entity] = []
+
+        # Filling in entity ids and generating points for dual basis
+        nf = dglagrange.space_dimension()
+        points = []
+        num_facets = sd + 1
+        for f in range(num_facets):
+            entity_dofs[sd - 1][f] = range(f * nf, (f + 1) * nf)
+
+            for dof in dglagrange.dual_basis():
+                facet_point = list(dof.get_point_dict().keys())[0]
+                transform = ref_el.get_entity_transform(sd - 1, f)
+                points.append(tuple(transform(facet_point)))
+
+        # Setting up dual basis - only point evaluations
+        nodes = [PointEvaluation(ref_el, pt) for pt in points]
+        dual = dual_set.DualSet(nodes, ref_el, entity_dofs)
+
+        super(HDivTrace, self).__init__(ref_el, dual, dglagrange.get_order(),
+                                        dglagrange.get_formdegree(), dglagrange.mapping()[0])
+        # Set up facet element
+        self.facet_element = dglagrange
+
+        # degree for quadrature rule
+        self.polydegree = degree
+
+    def degree(self):
+        """Return the degree of the (embedding) polynomial space."""
+        return self.polydegree
+
+    def get_nodal_basis(self):
+        """Return the nodal basis, encoded as a PolynomialSet object,
+        for the finite element."""
+        raise NotImplementedError("get_nodal_basis not implemented for the trace element.")
+
+    def get_coeffs(self):
+        """Return the expansion coefficients for the basis of the
+        finite element."""
+        raise NotImplementedError("get_coeffs not implemented for the trace element.")
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to a given order of
+        basis functions at given points.
+
+        :arg order: The maximum order of derivative.
+        :arg points: An iterable of points.
+        :arg entity: Optional (dimension, entity number) pair
+                     indicating which topological entity of the
+                     reference element to tabulate on.  If ``None``,
+                     tabulated values are computed by geometrically
+                     approximating which facet the points are on.
+        """
+        facet_dim = self.ref_el.get_spatial_dimension() - 1
+        sdim = self.space_dimension()
+        nf = self.facet_element.space_dimension()
+
+        # Initializing dictionary with zeros
+        phivals = {}
+        for i in range(order + 1):
+            alphas = mis(self.ref_el.get_spatial_dimension(), i)
+            for alpha in alphas:
+                phivals[alpha] = np.zeros(shape=(sdim, len(points)))
+        evalkey = (0,) * (facet_dim + 1)
+
+        # If entity is None, identify facet using numerical tolerance and
+        # return the tabulated values
+        if entity is None:
+            # Attempt to identify which facet (if any) the given points are on
+            vertices = self.ref_el.vertices
+            coordinates = barycentric_coordinates(points, vertices)
+            (unique_facet, success) = extract_unique_facet(coordinates)
+
+            # If we are not successful in finding a unique facet, raise an exception
+            if not success:
+                raise TraceError("Could not find a unique facet to tabulate on.")
+
+            # Map points to the reference facet
+            new_points = map_to_reference_facet(points, vertices, unique_facet)
+
+            # Retrieve values by tabulating the DiscontinuousLagrange element
+            nonzerovals = list(self.facet_element.tabulate(order, new_points).values())[0]
+            phivals[evalkey][nf*unique_facet:nf*(unique_facet + 1), :] = nonzerovals
+
+            return phivals
+
+        entity_dim, entity_id = entity
+
+        # If the user is directly specifying cell-wise tabulation, return TraceErrors in dict for
+        # appropriate handling in the form compiler
+        if entity_dim != facet_dim:
+            for key in phivals.keys():
+                phivals[key] = TraceError("Attempting to tabulate a %d-entity. Expecting a %d-entitiy" % (entity_dim, facet_dim))
+            return phivals
+
+        else:
+            # Retrieve function evaluations (order = 0 case)
+            nonzerovals = list(self.facet_element.tabulate(0, points).values())[0]
+            phivals[evalkey][nf*entity_id:nf*(entity_id + 1), :] = nonzerovals
+
+            # If asking for gradient evaluations, insert TraceError in gradient evaluations
+            if order > 0:
+                for key in phivals.keys():
+                    if key != evalkey:
+                        phivals[key] = TraceError("Gradient evaluations are illegal on trace elements.")
+            return phivals
+
+    def value_shape(self):
+        """Return the value shape of the finite element functions."""
+        return self.facet_element.value_shape()
+
+    def dmats(self):
+        """Return dmats: expansion coefficients for basis function
+        derivatives."""
+        raise NotImplementedError("dmats not implemented for the trace element.")
+
+    def get_num_members(self, arg):
+        """Return number of members of the expansion set."""
+        raise NotImplementedError("get_num_members not implemented for the trace element.")
+
+
+# The following functions are credited to Marie E. Rognes:
+def extract_unique_facet(coordinates, tolerance=epsilon):
+    """Determines whether a set of points (described in its barycentric coordinates)
+    are all on one of the facet sub-entities, and return the particular facet and
+    whether the search has been successful.
+
+    :arg coordinates: A set of points described in barycentric coordinates.
+    :arg tolerance: A fixed tolerance for geometric identifications.
+    """
+    facets = []
+    for c in coordinates:
+        on_facet = set([i for (i, l) in enumerate(c) if abs(l) < tolerance])
+        facets += [on_facet]
+
+    unique_facet = facets[0]
+    for f in facets:
+        unique_facet = unique_facet & f
+
+    # Handle coordinates not on facets
+    if len(unique_facet) != 1:
+        return (None, False)
+
+    # If we have a unique facet, return it and success
+    return (unique_facet.pop(), True)
+
+
+def barycentric_coordinates(points, vertices):
+    """Computes the barycentric coordinates for a set of points relative to a
+    simplex defined by a set of vertices.
+
+    :arg points: A set of points.
+    :arg vertices: A set of vertices that define the simplex.
+    """
+
+    # Form mapping matrix
+    last = np.asarray(vertices[-1])
+    T = np.matrix([np.array(v) - last for v in vertices[:-1]]).T
+    invT = np.linalg.inv(T)
+
+    # Compute the barycentric coordinates for all points
+    coords = []
+    for p in points:
+        y = np.asarray(p) - last
+        bary = invT.dot(y.T)
+        bary = [bary[(0, i)] for i in range(len(y))]
+        bary += [1.0 - sum(bary)]
+        coords.append(bary)
+    return coords
+
+
+def map_from_reference_facet(point, vertices):
+    """Evaluates the physical coordinate of a point using barycentric
+    coordinates.
+
+    :arg point: The reference points to be mapped to the facet.
+    :arg vertices: The vertices defining the physical element.
+    """
+
+    # Compute the barycentric coordinates of the point relative to the reference facet
+    reference_simplex = ufc_simplex(len(vertices) - 1)
+    reference_vertices = reference_simplex.get_vertices()
+    coords = barycentric_coordinates([point, ], reference_vertices)[0]
+
+    # Evaluates the physical coordinate of the point using barycentric coordinates
+    point = sum(vertices[j] * coords[j] for j in range(len(coords)))
+    return tuple(point)
+
+
+def map_to_reference_facet(points, vertices, facet):
+    """Given a set of points and vertices describing a facet of a simplex in n-dimensional
+    coordinates (where the points lie on the facet), map the points to the reference simplex
+    of dimension (n-1).
+
+    :arg points: A set of points in n-D.
+    :arg vertices: A set of vertices describing a facet of a simplex in n-D.
+    :arg facet: Integer representing the facet number.
+    """
+
+    # Compute the barycentric coordinates of the points with respect to the
+    # full physical simplex
+    all_coords = barycentric_coordinates(points, vertices)
+
+    # Extract vertices of the reference facet
+    reference_facet_simplex = ufc_simplex(len(vertices) - 2)
+    reference_vertices = reference_facet_simplex.get_vertices()
+
+    reference_points = []
+    for (i, coords) in enumerate(all_coords):
+        # Extract the correct subset of barycentric coordinates since we know
+        # which facet we are on
+        new_coords = [coords[j] for j in range(len(coords)) if j != facet]
+
+        # Evaluate the reference coordinate of a point in barycentric coordinates
+        reference_pt = sum(np.asarray(reference_vertices[j]) * new_coords[j]
+                           for j in range(len(new_coords)))
+
+        reference_points += [reference_pt]
+    return reference_points
diff --git a/FIAT/hdivcurl.py b/FIAT/hdivcurl.py
new file mode 100644
index 0000000..49f6823
--- /dev/null
+++ b/FIAT/hdivcurl.py
@@ -0,0 +1,267 @@
+# Copyright (C) 2013 Andrew T. T. McRae (Imperial College London)
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import numpy
+import types
+from FIAT.tensor_product import TensorProductElement
+from FIAT import functional
+
+
+def Hdiv(element):
+    if not isinstance(element, TensorProductElement):
+        raise NotImplementedError
+
+    if element.A.get_formdegree() is None or element.B.get_formdegree() is None:
+        raise ValueError("form degree of sub-element was None (not set during initialisation), Hdiv cannot be done without this information")
+    formdegree = element.A.get_formdegree() + element.B.get_formdegree()
+    if formdegree != element.get_reference_element().get_spatial_dimension() - 1:
+        raise ValueError("Tried to use Hdiv on a non-(n-1)-form element")
+
+    newelement = TensorProductElement(element.A, element.B)  # make a copy to return
+
+    # redefine value_shape()
+    def value_shape(self):
+        "Return the value shape of the finite element functions."
+        return (self.get_reference_element().get_spatial_dimension(),)
+    newelement.value_shape = types.MethodType(value_shape, newelement)
+
+    # store old _mapping
+    newelement._oldmapping = newelement._mapping
+
+    # redefine _mapping
+    newelement._mapping = "contravariant piola"
+
+    # store formdegree
+    newelement.formdegree = formdegree
+
+    # redefine tabulate
+    newelement.old_tabulate = newelement.tabulate
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points."""
+
+        # don't duplicate what the old function does fine...
+        old_result = self.old_tabulate(order, points, entity)
+        new_result = {}
+        sd = self.get_reference_element().get_spatial_dimension()
+        for alpha in old_result.keys():
+            temp_old = old_result[alpha]
+
+            if self._oldmapping == "affine":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[1]), dtype=temp_old.dtype)
+                # both constituents affine, i.e., they were 0 forms or n-forms.
+                # to sum to n-1, we must have "0-form on an interval" crossed
+                # with something discontinuous.
+                # look for the (continuous) 0-form, and put the value there
+                if self.A.get_formdegree() == 0:
+                    # first element, so (-x, 0, ...)
+                    # Sign flip to ensure that a positive value of the node
+                    # means a vector field having a direction "to the left"
+                    # relative to direction in which the nodes are placed on an
+                    # edge in case of higher-order schemes.
+                    # This is required for unstructured quadrilateral meshes.
+                    temp[:, 0, :] = -temp_old[:, :]
+                elif self.B.get_formdegree() == 0:
+                    # second element, so (..., 0, x)
+                    temp[:, -1, :] = temp_old[:, :]
+                else:
+                    raise Exception("Hdiv affine/affine form degrees broke")
+
+            elif self._oldmapping == "contravariant piola":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[2]), dtype=temp_old.dtype)
+                Asd = self.A.get_reference_element().get_spatial_dimension()
+                # one component is affine, one is contravariant piola
+                # the affine one must be an n-form, hence discontinuous
+                # this component/these components get zeroed out
+                if element.A.mapping()[0] == "contravariant piola":
+                    # first element, so (x1, ..., xn, 0, ...)
+                    temp[:, :Asd, :] = temp_old[:, :, :]
+                elif element.B.mapping()[0] == "contravariant piola":
+                    # second element, so (..., 0, x1, ..., xn)
+                    temp[:, Asd:, :] = temp_old[:, :, :]
+                else:
+                    raise ValueError("Hdiv contravariant piola couldn't find an existing ConPi subelement")
+
+            elif self._oldmapping == "covariant piola":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[2]), dtype=temp_old.dtype)
+                # one component is affine, one is covariant piola
+                # the affine one must be an n-form, hence discontinuous
+                # this component/these components get zeroed out
+                # the remaining part gets perped
+                if element.A.mapping()[0] == "covariant piola":
+                    Asd = self.A.get_reference_element().get_spatial_dimension()
+                    if not Asd == 2:
+                        raise ValueError("Must be 2d shape to automatically convert covariant to contravariant")
+                    temp_perp = numpy.zeros(temp_old.shape, dtype=temp_old.dtype)
+                    # first element, so (x2, -x1, 0, ...)
+                    temp_perp[:, 0, :] = temp_old[:, 1, :]
+                    temp_perp[:, 1, :] = -temp_old[:, 0, :]
+                    temp[:, :Asd, :] = temp_perp[:, :, :]
+                elif element.B.mapping()[0] == "covariant piola":
+                    Bsd = self.B.get_reference_element().get_spatial_dimension()
+                    if not Bsd == 2:
+                        raise ValueError("Must be 2d shape to automatically convert covariant to contravariant")
+                    temp_perp = numpy.zeros(temp_old.shape, dtype=temp_old.dtype)
+                    # second element, so (..., 0, x2, -x1)
+                    temp_perp[:, 0, :] = temp_old[:, 1, :]
+                    temp_perp[:, 1, :] = -temp_old[:, 0, :]
+                    temp[:, Asd:, :] = temp_old[:, :, :]
+                else:
+                    raise ValueError("Hdiv covariant piola couldn't find an existing CovPi subelement")
+            new_result[alpha] = temp
+        return new_result
+
+    newelement.tabulate = types.MethodType(tabulate, newelement)
+
+    # splat any PointEvaluation functionals.
+    # they become a nasty mix of internal and external component DOFs
+    if newelement._oldmapping == "affine":
+        oldnodes = newelement.dual.nodes
+        newnodes = []
+        for node in oldnodes:
+            if isinstance(node, functional.PointEvaluation):
+                newnodes.append(functional.Functional(None, None, None, {}, "Undefined"))
+            else:
+                newnodes.append(node)
+        newelement.dual.nodes = newnodes
+
+    return newelement
+
+
+def Hcurl(element):
+    if not isinstance(element, TensorProductElement):
+        raise NotImplementedError
+
+    if element.A.get_formdegree() is None or element.B.get_formdegree() is None:
+        raise ValueError("form degree of sub-element was None (not set during initialisation), Hcurl cannot be done without this information")
+    formdegree = element.A.get_formdegree() + element.B.get_formdegree()
+    if not (formdegree == 1):
+        raise ValueError("Tried to use Hcurl on a non-1-form element")
+
+    newelement = TensorProductElement(element.A, element.B)  # make a copy to return
+
+    # redefine value_shape()
+    def value_shape(self):
+        "Return the value shape of the finite element functions."
+        return (self.get_reference_element().get_spatial_dimension(),)
+    newelement.value_shape = types.MethodType(value_shape, newelement)
+
+    # store old _mapping
+    newelement._oldmapping = newelement._mapping
+
+    # redefine _mapping
+    newelement._mapping = "covariant piola"
+
+    # store formdegree
+    newelement.formdegree = formdegree
+
+    # redefine tabulate
+    newelement.old_tabulate = newelement.tabulate
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points."""
+
+        # don't duplicate what the old function does fine...
+        old_result = self.old_tabulate(order, points, entity)
+        new_result = {}
+        sd = self.get_reference_element().get_spatial_dimension()
+        for alpha in old_result.keys():
+            temp_old = old_result[alpha]
+
+            if self._oldmapping == "affine":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[1]), dtype=temp_old.dtype)
+                # both constituents affine, i.e., they were 0 forms or n-forms.
+                # to sum to 1, we must have "1-form on an interval" crossed with
+                # a bunch of 0-forms (continuous).
+                # look for the 1-form, and put the value in the other place
+                if self.A.get_formdegree() == 1:
+                    # first element, so (x, 0, ...)
+                    # No sign flip here, nor at the other branch, to ensure that
+                    # a positive value of the node means a vector field having
+                    # the same direction as the direction in which the nodes are
+                    # placed on an edge in case of higher-order schemes.
+                    # This is required for unstructured quadrilateral meshes.
+                    temp[:, 0, :] = temp_old[:, :]
+                elif self.B.get_formdegree() == 1:
+                    # second element, so (..., 0, x)
+                    temp[:, -1, :] = temp_old[:, :]
+                else:
+                    raise Exception("Hcurl affine/affine form degrees broke")
+
+            elif self._oldmapping == "covariant piola":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[2]), dtype=temp_old.dtype)
+                Asd = self.A.get_reference_element().get_spatial_dimension()
+                # one component is affine, one is covariant piola
+                # the affine one must be an 0-form, hence continuous
+                # this component/these components get zeroed out
+                if element.A.mapping()[0] == "covariant piola":
+                    # first element, so (x1, ..., xn, 0, ...)
+                    temp[:, :Asd, :] = temp_old[:, :, :]
+                elif element.B.mapping()[0] == "covariant piola":
+                    # second element, so (..., 0, x1, ..., xn)
+                    temp[:, Asd:, :] = temp_old[:, :, :]
+                else:
+                    raise ValueError("Hdiv contravariant piola couldn't find an existing ConPi subelement")
+
+            elif self._oldmapping == "contravariant piola":
+                temp = numpy.zeros((temp_old.shape[0], sd, temp_old.shape[2]), dtype=temp_old.dtype)
+                # one component is affine, one is contravariant piola
+                # the affine one must be an 0-form, hence continuous
+                # this component/these components get zeroed out
+                # the remaining part gets perped
+                if element.A.mapping()[0] == "contravariant piola":
+                    Asd = self.A.get_reference_element().get_spatial_dimension()
+                    if not Asd == 2:
+                        raise ValueError("Must be 2d shape to automatically convert contravariant to covariant")
+                    temp_perp = numpy.zeros(temp_old.shape, dtype=temp_old.dtype)
+                    # first element, so (-x2, x1, 0, ...)
+                    temp_perp[:, 0, :] = -temp_old[:, 1, :]
+                    temp_perp[:, 1, :] = temp_old[:, 0, :]
+                    temp[:, :Asd, :] = temp_perp[:, :, :]
+                elif element.B.mapping()[0] == "contravariant piola":
+                    Bsd = self.B.get_reference_element().get_spatial_dimension()
+                    if not Bsd == 2:
+                        raise ValueError("Must be 2d shape to automatically convert contravariant to covariant")
+                    temp_perp = numpy.zeros(temp_old.shape, dtype=temp_old.dtype)
+                    # second element, so (..., 0, -x2, x1)
+                    temp_perp[:, 0, :] = -temp_old[:, 1, :]
+                    temp_perp[:, 1, :] = temp_old[:, 0, :]
+                    temp[:, Asd:, :] = temp_old[:, :, :]
+                else:
+                    raise ValueError("Hcurl contravariant piola couldn't find an existing CovPi subelement")
+            new_result[alpha] = temp
+        return new_result
+
+    newelement.tabulate = types.MethodType(tabulate, newelement)
+
+    # splat any PointEvaluation functionals.
+    # they become a nasty mix of internal and external component DOFs
+    if newelement._oldmapping == "affine":
+        oldnodes = newelement.dual.nodes
+        newnodes = []
+        for node in oldnodes:
+            if isinstance(node, functional.PointEvaluation):
+                newnodes.append(functional.Functional(None, None, None, {}, "Undefined"))
+            else:
+                newnodes.append(node)
+        newelement.dual.nodes = newnodes
+
+    return newelement
diff --git a/FIAT/hellan_herrmann_johnson.py b/FIAT/hellan_herrmann_johnson.py
new file mode 100644
index 0000000..5e482b1
--- /dev/null
+++ b/FIAT/hellan_herrmann_johnson.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+"""Implementation of the Hellan-Herrmann-Johnson finite elements."""
+
+# Copyright (C) 2016-2018 Lizao Li <lzlarryli at gmail.com>
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+from FIAT.finite_element import CiarletElement
+from FIAT.dual_set import DualSet
+from FIAT.polynomial_set import ONSymTensorPolynomialSet
+from FIAT.functional import PointwiseInnerProductEvaluation as InnerProduct
+import numpy
+
+
+class HellanHerrmannJohnsonDual(DualSet):
+    """Degrees of freedom for Hellan-Herrmann-Johnson elements."""
+    def __init__(self, cell, degree):
+        dim = cell.get_spatial_dimension()
+        if not dim == 2:
+            raise ValueError("Hellan_Herrmann-Johnson elements are only"
+                             "defined in dimension 2.")
+
+        # construct the degrees of freedoms
+        dofs = []               # list of functionals
+        # dof_ids[i][j] contains the indices of dofs that are associated with
+        # entity j in dim i
+        dof_ids = {}
+
+        # no vertex dof
+        dof_ids[0] = {i: [] for i in range(dim + 1)}
+        # edge dofs
+        (_dofs, _dof_ids) = self._generate_edge_dofs(cell, degree, 0)
+        dofs.extend(_dofs)
+        dof_ids[1] = _dof_ids
+        # cell dofs
+        (_dofs, _dof_ids) = self._generate_trig_dofs(cell, degree, len(dofs))
+        dofs.extend(_dofs)
+        dof_ids[dim] = _dof_ids
+
+        super(HellanHerrmannJohnsonDual, self).__init__(dofs, cell, dof_ids)
+
+    @staticmethod
+    def _generate_edge_dofs(cell, degree, offset):
+        """generate dofs on edges.
+        On each edge, let n be its normal. For degree=r, the scalar function
+              n^T u n
+        is evaluated at points enough to control P(r).
+        """
+        dofs = []
+        dof_ids = {}
+        for entity_id in range(3):                  # a triangle has 3 edges
+            pts = cell.make_points(1, entity_id, degree + 2)  # edges are 1D
+            normal = cell.compute_scaled_normal(entity_id)
+            dofs += [InnerProduct(cell, normal, normal, pt) for pt in pts]
+            num_new_dofs = len(pts)                 # 1 dof per point on edge
+            dof_ids[entity_id] = list(range(offset, offset + num_new_dofs))
+            offset += num_new_dofs
+        return (dofs, dof_ids)
+
+    @staticmethod
+    def _generate_trig_dofs(cell, degree, offset):
+        """generate dofs on edges.
+        On each triangle, for degree=r, the three components
+              u11, u12, u22
+        are evaluated at points enough to control P(r-1).
+        """
+        dofs = []
+        dof_ids = {}
+        pts = cell.make_points(2, 0, degree + 2)  # 2D trig #0
+        e1 = numpy.array([1.0, 0.0])              # euclidean basis 1
+        e2 = numpy.array([0.0, 1.0])              # euclidean basis 2
+        basis = [(e1, e1), (e1, e2), (e2, e2)]    # basis for symmetric matrix
+        for (v1, v2) in basis:
+            dofs += [InnerProduct(cell, v1, v2, pt) for pt in pts]
+        num_dofs = 3 * len(pts)                   # 3 dofs per trig
+        dof_ids[0] = list(range(offset, offset + num_dofs))
+        return (dofs, dof_ids)
+
+
+class HellanHerrmannJohnson(CiarletElement):
+    """The definition of Hellan-Herrmann-Johnson element. It is defined only in
+       dimension 2. It consists of piecewise polynomial symmetric-matrix-valued
+       functions of degree r or less with normal-normal continuity.
+    """
+    def __init__(self, cell, degree):
+        assert degree >= 0, "Hellan-Herrmann-Johnson starts at degree 0!"
+        # shape functions
+        Ps = ONSymTensorPolynomialSet(cell, degree)
+        # degrees of freedom
+        Ls = HellanHerrmannJohnsonDual(cell, degree)
+        # mapping under affine transformation
+        mapping = "double contravariant piola"
+
+        super(HellanHerrmannJohnson, self).__init__(Ps, Ls, degree,
+                                                    mapping=mapping)
diff --git a/FIAT/hermite.py b/FIAT/hermite.py
index 91ed91d..9f2f3d1 100644
--- a/FIAT/hermite.py
+++ b/FIAT/hermite.py
@@ -15,13 +15,17 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, polynomial_set, dual_set, functional
+from __future__ import absolute_import, print_function, division
 
-class CubicHermiteDualSet( dual_set.DualSet ):
+from FIAT import finite_element, polynomial_set, dual_set, functional
+
+
+class CubicHermiteDualSet(dual_set.DualSet):
     """The dual basis for Lagrange elements.  This class works for
     simplices of any dimension.  Nodes are point evaluation at
     equispaced points."""
-    def __init__( self, ref_el ):
+
+    def __init__(self, ref_el):
         entity_ids = {}
         nodes = []
         cur = 0
@@ -35,53 +39,43 @@ class CubicHermiteDualSet( dual_set.DualSet ):
         # get jet at each vertex
 
         entity_ids[0] = {}
-        for v in sorted( top[0] ):
-            nodes.append( functional.PointEvaluation( ref_el, verts[v] ) )
+        for v in sorted(top[0]):
+            nodes.append(functional.PointEvaluation(ref_el, verts[v]))
             pd = functional.PointDerivative
-            for i in range( sd ):
+            for i in range(sd):
                 alpha = [0] * sd
                 alpha[i] = 1
-                
-                nodes.append( pd( ref_el, verts[v], alpha ) )
 
-            entity_ids[0][v] = list(range(cur, cur+1+sd))
+                nodes.append(pd(ref_el, verts[v], alpha))
+
+            entity_ids[0][v] = list(range(cur, cur + 1 + sd))
             cur += sd + 1
-                          
+
         # no edge dof
         entity_ids[1] = {}
-        
+
         # face dof
         # point evaluation at barycenter
         entity_ids[2] = {}
-        for f in sorted( top[2] ):
-            pt = ref_el.make_points( 2, f, 3 )[0]
-            n = functional.PointEvaluation( ref_el, pt )
-            nodes.append( n )
-            entity_ids[2] = list(range(cur, cur+1))
+        for f in sorted(top[2]):
+            pt = ref_el.make_points(2, f, 3)[0]
+            n = functional.PointEvaluation(ref_el, pt)
+            nodes.append(n)
+            entity_ids[2] = list(range(cur, cur + 1))
             cur += 1
-            
 
-        for dim in range(3, sd+1):
+        for dim in range(3, sd + 1):
             entity_ids[dim] = {}
             for facet in top[dim]:
                 entity_ids[dim][facet] = []
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
-
-class CubicHermite( finite_element.FiniteElement ):
-    """The Lagrange finite element.  It is what it is."""
-    def __init__( self, ref_el ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, 3 )
-        dual = CubicHermiteDualSet( ref_el  )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, 3 )
+        super(CubicHermiteDualSet, self).__init__(nodes, ref_el, entity_ids)
 
-if __name__=="__main__":
-    from . import reference_element
-    T = reference_element.DefaultTetrahedron()
-    U = CubicHermite( T )
 
-    Ufs = U.get_nodal_basis()
-    pts = T.make_lattice( 3 )
-    print(pts)
-    print(list(Ufs.tabulate(pts).values())[0])
+class CubicHermite(finite_element.CiarletElement):
+    """The cubic Hermite finite element.  It is what it is."""
 
+    def __init__(self, ref_el):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, 3)
+        dual = CubicHermiteDualSet(ref_el)
+        super(CubicHermite, self).__init__(poly_set, dual, 3)
diff --git a/FIAT/jacobi.py b/FIAT/jacobi.py
index 59b631b..46c7db2 100644
--- a/FIAT/jacobi.py
+++ b/FIAT/jacobi.py
@@ -14,14 +14,16 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
 """Several functions related to the one-dimensional jacobi polynomials:
 Evaluation, evaluation of derivatives, plus computation of the roots
 via Newton's method.  These mainly are used in defining the expansion
 functions over the simplices and in defining quadrature
 rules over each domain."""
 
-import math, numpy
+from __future__ import absolute_import, print_function, division
+
+import numpy
+
 
 def eval_jacobi(a, b, n, x):
     """Evaluates the nth jacobi polynomial with weight parameters a,b at a
@@ -29,37 +31,38 @@ def eval_jacobi(a, b, n, x):
     given in Karniadakis and Sherwin, Appendix B"""
 
     if 0 == n:
-        return 1.0;
+        return 1.0
     elif 1 == n:
-        return 0.5 * ( a - b + ( a + b + 2.0 ) * x )
-    else: # 2 <= n
+        return 0.5 * (a - b + (a + b + 2.0) * x)
+    else:  # 2 <= n
         apb = a + b
         pn2 = 1.0
-        pn1 = 0.5 * ( a - b + ( apb + 2.0 ) * x )
+        pn1 = 0.5 * (a - b + (apb + 2.0) * x)
         p = 0
-        for k in range(2, n+1):
-            a1 = 2.0 * k * ( k + apb ) * ( 2.0 * k + apb - 2.0 )
-            a2 = ( 2.0 * k + apb - 1.0 ) * ( a * a - b * b )
-            a3 = ( 2.0 * k + apb - 2.0 )  \
-                 * ( 2.0 * k + apb - 1.0 ) \
-                 * ( 2.0 * k + apb )
-            a4 = 2.0 * ( k + a - 1.0 ) * ( k + b - 1.0 ) \
-                 * ( 2.0 * k + apb )
+        for k in range(2, n + 1):
+            a1 = 2.0 * k * (k + apb) * (2.0 * k + apb - 2.0)
+            a2 = (2.0 * k + apb - 1.0) * (a * a - b * b)
+            a3 = (2.0 * k + apb - 2.0)  \
+                * (2.0 * k + apb - 1.0) \
+                * (2.0 * k + apb)
+            a4 = 2.0 * (k + a - 1.0) * (k + b - 1.0) \
+                * (2.0 * k + apb)
             a2 = a2 / a1
             a3 = a3 / a1
             a4 = a4 / a1
-            p = ( a2 + a3 * x ) * pn1 - a4 * pn2
+            p = (a2 + a3 * x) * pn1 - a4 * pn2
             pn2 = pn1
             pn1 = p
         return p
 
+
 def eval_jacobi_batch(a, b, n, xs):
     """Evaluates all jacobi polynomials with weights a,b
     up to degree n.  xs is a numpy.array of points.
     Returns a two-dimensional array of points, where the
     rows correspond to the Jacobi polynomials and the
     columns correspond to the points."""
-    result = numpy.zeros((n+1, len(xs)), xs.dtype)
+    result = numpy.zeros((n + 1, len(xs)), xs.dtype)
     # hack to make sure AD type is propogated through
     for ii in range(result.shape[1]):
         result[0, ii] = 1.0 + xs[ii, 0] - xs[ii, 0]
@@ -67,30 +70,32 @@ def eval_jacobi_batch(a, b, n, xs):
     xsnew = xs.reshape((-1,))
 
     if n > 0:
-        result[1,:] = 0.5 * ( a - b + ( a + b + 2.0 ) * xsnew )
-    
+        result[1, :] = 0.5 * (a - b + (a + b + 2.0) * xsnew)
+
         apb = a + b
-        for k in range(2, n+1):
-            a1 = 2.0 * k * ( k + apb ) * ( 2.0 * k + apb - 2.0 )
-            a2 = ( 2.0 * k + apb - 1.0 ) * ( a * a - b * b )
-            a3 = ( 2.0 * k + apb - 2.0 )  \
-                * ( 2.0 * k + apb - 1.0 ) \
-                * ( 2.0 * k + apb )
-            a4 = 2.0 * ( k + a - 1.0 ) * ( k + b - 1.0 ) \
-                * ( 2.0 * k + apb )
+        for k in range(2, n + 1):
+            a1 = 2.0 * k * (k + apb) * (2.0 * k + apb - 2.0)
+            a2 = (2.0 * k + apb - 1.0) * (a * a - b * b)
+            a3 = (2.0 * k + apb - 2.0)  \
+                * (2.0 * k + apb - 1.0) \
+                * (2.0 * k + apb)
+            a4 = 2.0 * (k + a - 1.0) * (k + b - 1.0) \
+                * (2.0 * k + apb)
             a2 = a2 / a1
             a3 = a3 / a1
             a4 = a4 / a1
-            result[k,:] = ( a2 + a3 * xsnew ) * result[k-1,:] \
-                - a4 * result[k-2,:]
+            result[k, :] = (a2 + a3 * xsnew) * result[k-1, :] \
+                - a4 * result[k-2, :]
     return result
 
+
 def eval_jacobi_deriv(a, b, n, x):
     """Evaluates the first derivative of P_{n}^{a,b} at a point x."""
     if n == 0:
         return 0.0
     else:
-        return 0.5 * ( a + b + n + 1 ) * eval_jacobi(a+1, b+1, n-1, x)
+        return 0.5 * (a + b + n + 1) * eval_jacobi(a + 1, b + 1, n - 1, x)
+
 
 def eval_jacobi_deriv_batch(a, b, n, xs):
     """Evaluates the first derivatives of all jacobi polynomials with
@@ -98,13 +103,11 @@ def eval_jacobi_deriv_batch(a, b, n, xs):
     Returns a two-dimensional array of points, where the
     rows correspond to the Jacobi polynomials and the
     columns correspond to the points."""
-    results = numpy.zeros( (n+1, len(xs)), "d" )
+    results = numpy.zeros((n + 1, len(xs)), "d")
     if n == 0:
         return results
     else:
-        results[1:,:] = eval_jacobi_batch(a+1, b+1, n-1, xs)
-    for j in range(1, n+1):
-        results[j,:] *= 0.5*(a+b+j+1)
+        results[1:, :] = eval_jacobi_batch(a + 1, b + 1, n - 1, xs)
+    for j in range(1, n + 1):
+        results[j, :] *= 0.5 * (a + b + j + 1)
     return results
-
-
diff --git a/FIAT/lagrange.py b/FIAT/lagrange.py
index 99daac6..0f5ec8b 100644
--- a/FIAT/lagrange.py
+++ b/FIAT/lagrange.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,13 +16,17 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, polynomial_set, dual_set, functional
+from __future__ import absolute_import, print_function, division
 
-class LagrangeDualSet( dual_set.DualSet ):
+from FIAT import finite_element, polynomial_set, dual_set, functional
+
+
+class LagrangeDualSet(dual_set.DualSet):
     """The dual basis for Lagrange elements.  This class works for
     simplices of any dimension.  Nodes are point evaluation at
     equispaced points."""
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         entity_ids = {}
         nodes = []
 
@@ -30,41 +35,25 @@ class LagrangeDualSet( dual_set.DualSet ):
         top = ref_el.get_topology()
 
         cur = 0
-        for dim in sorted( top ):
+        for dim in sorted(top):
             entity_ids[dim] = {}
-            for entity in sorted( top[dim] ):
-                pts_cur = ref_el.make_points( dim, entity, degree )
-                nodes_cur = [ functional.PointEvaluation( ref_el, x ) \
-                              for x in pts_cur ]
-                nnodes_cur = len( nodes_cur )
-                nodes +=  nodes_cur
-                entity_ids[dim][entity] = list(range(cur, cur+nnodes_cur))
+            for entity in sorted(top[dim]):
+                pts_cur = ref_el.make_points(dim, entity, degree)
+                nodes_cur = [functional.PointEvaluation(ref_el, x)
+                             for x in pts_cur]
+                nnodes_cur = len(nodes_cur)
+                nodes += nodes_cur
+                entity_ids[dim][entity] = list(range(cur, cur + nnodes_cur))
                 cur += nnodes_cur
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(LagrangeDualSet, self).__init__(nodes, ref_el, entity_ids)
 
-class Lagrange( finite_element.FiniteElement ):
-    """The Lagrange finite element.  It is what it is."""
-    def __init__( self, ref_el, degree ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, degree )
-        dual = LagrangeDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree )
-
-if __name__=="__main__":
-    from . import reference_element
-    # UFC triangle and points
-    T = reference_element.UFCTriangle()
-    pts = T.make_lattice(1)
-   # pts = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]
 
-    # FIAT triangle and points
-#    T = reference_element.DefaultTriangle()
-#    pts = [(-1.0, -1.0), (1.0, -1.0), (-1.0, 1.0)]
+class Lagrange(finite_element.CiarletElement):
+    """The Lagrange finite element.  It is what it is."""
 
-    L = Lagrange(T, 1)
-    Ufs = L.get_nodal_basis()
-    print(pts)
-    for foo, bar in list(Ufs.tabulate( pts, 1 ).items()):
-        print(foo)
-        print(bar)
-        print()
+    def __init__(self, ref_el, degree):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
+        dual = LagrangeDualSet(ref_el, degree)
+        formdegree = 0  # 0-form
+        super(Lagrange, self).__init__(poly_set, dual, degree, formdegree)
diff --git a/FIAT/makelags.py b/FIAT/makelags.py
deleted file mode 100644
index ef6a25c..0000000
--- a/FIAT/makelags.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from . import lagrange
-from . import reference_element
-import string
-import numpy
-
-lagclass = \
-"""class Lagrange%s%d: public FiniteElement {
-public:
-  Lagrange%s%d():FiniteElement(%d,%d,%d,%d,%d,%s) {}
-  virtual ~Lagrange%s%d(){}
-};"""
-
-def array_to_C_string( u ):
-    x = [ str( a ) for a in u ]
-    return "{ %s }" % ( string.join( x, " , " ) )
-
-def matrix_to_array( mat, mat_name ):
-    (num_rows, num_cols) = mat.shape
-
-    # get C array of data
-    u = numpy.ravel( numpy.transpose( mat ) )
-
-    array_name = mat_name
-    return \
-"""static double %s[] = %s;""" % ( array_name, \
-        array_to_C_string( u ) )
-
-T = reference_element.DefaultTriangle()
-shape = "Triangle"
-for i in range(3, 4):
-    L = lagrange.Lagrange(T, i)
-    nb = L.get_nodal_basis()
-    vdm = nb.get_coeffs()
-    array_name="Lagrange%s%dCoeffs"%(shape, i)
-    print(matrix_to_array( vdm, array_name ))
-    print(lagclass % (shape, i, shape, i,\
-                          nb.get_degree(), \
-                          nb.get_embedded_degree(), \
-                          2,\
-                          nb.get_num_members(), \
-                          nb.get_num_members(), \
-                          array_name, shape, i))
-
diff --git a/FIAT/morley.py b/FIAT/morley.py
index d087aa6..7a56158 100644
--- a/FIAT/morley.py
+++ b/FIAT/morley.py
@@ -15,13 +15,17 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import finite_element, polynomial_set, dual_set, functional
+from __future__ import absolute_import, print_function, division
 
-class MorleyDualSet( dual_set.DualSet ):
+from FIAT import finite_element, polynomial_set, dual_set, functional
+
+
+class MorleyDualSet(dual_set.DualSet):
     """The dual basis for Lagrange elements.  This class works for
     simplices of any dimension.  Nodes are point evaluation at
     equispaced points."""
-    def __init__( self, ref_el ):
+
+    def __init__(self, ref_el):
         entity_ids = {}
         nodes = []
         cur = 0
@@ -34,42 +38,31 @@ class MorleyDualSet( dual_set.DualSet ):
         if sd != 2:
             raise Exception("Illegal spatial dimension")
 
-        pd = functional.PointDerivative
-
         # vertex point evaluations
 
         entity_ids[0] = {}
-        for v in sorted( top[0] ):
-            nodes.append( functional.PointEvaluation( ref_el, verts[v] ) )
+        for v in sorted(top[0]):
+            nodes.append(functional.PointEvaluation(ref_el, verts[v]))
 
             entity_ids[0][v] = [cur]
             cur += 1
-                          
+
         # edge dof -- normal at each edge midpoint
         entity_ids[1] = {}
-        for e in sorted( top[1] ):
-            pt = ref_el.make_points( 1, e, 2 )[0]
-            n = functional.PointNormalDerivative( ref_el, e, pt )
-            nodes.append( n )
+        for e in sorted(top[1]):
+            pt = ref_el.make_points(1, e, 2)[0]
+            n = functional.PointNormalDerivative(ref_el, e, pt)
+            nodes.append(n)
             entity_ids[1][e] = [cur]
             cur += 1
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
-
-class Morley( finite_element.FiniteElement ):
-    """The Morley finite element."""
-    def __init__( self, ref_el ):
-        poly_set = polynomial_set.ONPolynomialSet( ref_el, 2 )
-        dual = MorleyDualSet( ref_el  )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, 2 )
+        super(MorleyDualSet, self).__init__(nodes, ref_el, entity_ids)
 
-if __name__=="__main__":
-    from . import reference_element
-    T = reference_element.DefaultTriangle()
-    U = Morley( T )
 
-    Ufs = U.get_nodal_basis()
-    pts = T.make_lattice( 1 )
-    print(pts)
-    print(list(Ufs.tabulate(pts).values())[0])
+class Morley(finite_element.CiarletElement):
+    """The Morley finite element."""
 
+    def __init__(self, ref_el):
+        poly_set = polynomial_set.ONPolynomialSet(ref_el, 2)
+        dual = MorleyDualSet(ref_el)
+        super(Morley, self).__init__(poly_set, dual, 2)
diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py
index 30fb14c..e5b3804 100644
--- a/FIAT/nedelec.py
+++ b/FIAT/nedelec.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,57 +16,59 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import polynomial_set, expansions, quadrature, dual_set, \
-       finite_element, functional
-from functools import reduce
+from __future__ import absolute_import, print_function, division
+
+from FIAT import (polynomial_set, expansions, quadrature, dual_set,
+                  finite_element, functional)
+from itertools import chain
 import numpy
 
-def NedelecSpace2D( ref_el, k ):
+
+def NedelecSpace2D(ref_el, k):
     """Constructs a basis for the 2d H(curl) space of the first kind
     which is (P_k)^2 + P_k rot( x )"""
     sd = ref_el.get_spatial_dimension()
     if sd != 2:
         raise Exception("NedelecSpace2D requires 2d reference element")
 
-    vec_Pkp1 = polynomial_set.ONPolynomialSet( ref_el, k+1, (sd,) )
+    vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
 
-    dimPkp1 = expansions.polynomial_dimension( ref_el, k+1 )
-    dimPk = expansions.polynomial_dimension( ref_el, k )
-    dimPkm1 = expansions.polynomial_dimension( ref_el, k-1 )
+    dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
+    dimPk = expansions.polynomial_dimension(ref_el, k)
+    dimPkm1 = expansions.polynomial_dimension(ref_el, k - 1)
 
-    vec_Pk_indices = reduce( lambda a, b: a+b, \
-                             [ list(range(i*dimPkp1, i*dimPkp1+dimPk)) \
-                               for i in range(sd) ] )
-    vec_Pk_from_Pkp1 = vec_Pkp1.take( vec_Pk_indices )
+    vec_Pk_indices = list(chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk)
+                                  for i in range(sd))))
+    vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)
 
-    Pkp1 = polynomial_set.ONPolynomialSet( ref_el, k + 1 )
-    PkH = Pkp1.take( list(range(dimPkm1, dimPk)) )
+    Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
+    PkH = Pkp1.take(list(range(dimPkm1, dimPk)))
 
-    Q = quadrature.make_quadrature( ref_el, 2 * k + 2 )
+    Q = quadrature.make_quadrature(ref_el, 2 * k + 2)
 
-    Qpts = numpy.array( Q.get_points() )
-    Qwts = numpy.array( Q.get_weights() )
+    Qpts = numpy.array(Q.get_points())
+    Qwts = numpy.array(Q.get_weights())
 
-    zero_index = tuple( [ 0 for i in range(sd) ] )
+    zero_index = tuple([0 for i in range(sd)])
 
-    PkH_at_Qpts = PkH.tabulate( Qpts )[zero_index]
-    Pkp1_at_Qpts = Pkp1.tabulate( Qpts )[zero_index]
+    PkH_at_Qpts = PkH.tabulate(Qpts)[zero_index]
+    Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index]
 
-    PkH_crossx_coeffs = numpy.zeros( (PkH.get_num_members(), \
-                                      sd, \
-                                      Pkp1.get_num_members()), "d" )
+    PkH_crossx_coeffs = numpy.zeros((PkH.get_num_members(),
+                                     sd,
+                                     Pkp1.get_num_members()), "d")
 
-    def rot_x_foo( a ):
+    def rot_x_foo(a):
         if a == 0:
             return 1, 1.0
         elif a == 1:
             return 0, -1.0
 
-    for i in range( PkH.get_num_members() ):
-        for j in range( sd ):
-            (ind, sign) = rot_x_foo( j )
-            for k in range( Pkp1.get_num_members() ):
-                PkH_crossx_coeffs[i, j, k] = sign * sum( Qwts * PkH_at_Qpts[i,:] * Qpts[:, ind] * Pkp1_at_Qpts[k,:] )
+    for i in range(PkH.get_num_members()):
+        for j in range(sd):
+            (ind, sign) = rot_x_foo(j)
+            for k in range(Pkp1.get_num_members()):
+                PkH_crossx_coeffs[i, j, k] = sign * sum(Qwts * PkH_at_Qpts[i, :] * Qpts[:, ind] * Pkp1_at_Qpts[k, :])
 #                for l in range( len( Qpts ) ):
 #                    PkH_crossx_coeffs[i,j,k] += Qwts[ l ] \
 #                                                * PkH_at_Qpts[i,l] \
@@ -73,69 +76,63 @@ def NedelecSpace2D( ref_el, k ):
 #                                                * Pkp1_at_Qpts[k,l] \
 #                                                * sign
 
-    PkHcrossx = polynomial_set.PolynomialSet( ref_el, \
-                                              k + 1, \
-                                              k + 1, \
-                                              vec_Pkp1.get_expansion_set(), \
-                                              PkH_crossx_coeffs, \
-                                              vec_Pkp1.get_dmats() )
+    PkHcrossx = polynomial_set.PolynomialSet(ref_el,
+                                             k + 1,
+                                             k + 1,
+                                             vec_Pkp1.get_expansion_set(),
+                                             PkH_crossx_coeffs,
+                                             vec_Pkp1.get_dmats())
 
-    return polynomial_set.polynomial_set_union_normalized( vec_Pk_from_Pkp1, \
-                                                           PkHcrossx )
+    return polynomial_set.polynomial_set_union_normalized(vec_Pk_from_Pkp1,
+                                                          PkHcrossx)
 
 
-def NedelecSpace3D( ref_el, k ):
+def NedelecSpace3D(ref_el, k):
     """Constructs a nodal basis for the 3d first-kind Nedelec space"""
     sd = ref_el.get_spatial_dimension()
     if sd != 3:
         raise Exception("NedelecSpace3D requires 3d reference element")
 
+    vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
 
-    vec_Pkp1 = polynomial_set.ONPolynomialSet( ref_el, k + 1, \
-                                               (sd,) )
-
-    dimPkp1 = expansions.polynomial_dimension( ref_el, k + 1 )
-    dimPk = expansions.polynomial_dimension( ref_el, k )
+    dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
+    dimPk = expansions.polynomial_dimension(ref_el, k)
     if k > 0:
-        dimPkm1 = expansions.polynomial_dimension( ref_el, k - 1 )
+        dimPkm1 = expansions.polynomial_dimension(ref_el, k - 1)
     else:
         dimPkm1 = 0
 
-    vec_Pk_indices = reduce( lambda a, b: a + b, \
-                             [ list(range( i * dimPkp1, i * dimPkp1+dimPk)) \
-                               for i in range(sd) ] )
-    vec_Pk = vec_Pkp1.take( vec_Pk_indices )
-
-
-    vec_Pke_indices = reduce( lambda a, b : a + b, \
-                              [ list(range(i*dimPkp1+dimPkm1, i*dimPkp1+dimPk)) \
-                                for i in range(sd) ] )
+    vec_Pk_indices = list(chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk)
+                                  for i in range(sd))))
+    vec_Pk = vec_Pkp1.take(vec_Pk_indices)
 
-    vec_Pke = vec_Pkp1.take( vec_Pke_indices )
+    vec_Pke_indices = list(chain(*(range(i * dimPkp1 + dimPkm1, i * dimPkp1 + dimPk)
+                                   for i in range(sd))))
 
+    vec_Pke = vec_Pkp1.take(vec_Pke_indices)
 
-    Pkp1 = polynomial_set.ONPolynomialSet( ref_el, k + 1 )
+    Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
 
-    Q = quadrature.make_quadrature( ref_el, 2 * ( k + 1 ) )
+    Q = quadrature.make_quadrature(ref_el, 2 * (k + 1))
 
     Qpts = numpy.array(Q.get_points())
     Qwts = numpy.array(Q.get_weights())
 
-    zero_index = tuple( [ 0 for i in range(sd) ] )
+    zero_index = tuple([0 for i in range(sd)])
 
+    PkCrossXcoeffs = numpy.zeros((vec_Pke.get_num_members(),
+                                  sd,
+                                  Pkp1.get_num_members()), "d")
 
-    PkCrossXcoeffs = numpy.zeros( (vec_Pke.get_num_members(), \
-                                   sd, \
-                                   Pkp1.get_num_members()), "d" )
+    Pke_qpts = vec_Pke.tabulate(Qpts)[zero_index]
+    Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index]
 
-    Pke_qpts = vec_Pke.tabulate( Qpts )[zero_index]
-    Pkp1_at_Qpts = Pkp1.tabulate( Qpts )[ zero_index ]
-
-    for i in range( vec_Pke.get_num_members() ):
-        for j in range( sd ): # vector components
-            qwts_cur_bf_val = ( Qpts[:, (j+2)%3]*Pke_qpts[i, (j+1)%3,:] \
-                - Qpts[:, (j+1)%3] * Pke_qpts[i, (j+2)%3,:] ) * Qwts
-            PkCrossXcoeffs[i, j,:] = numpy.dot( Pkp1_at_Qpts, qwts_cur_bf_val )
+    for i in range(vec_Pke.get_num_members()):
+        for j in range(sd):  # vector components
+            qwts_cur_bf_val = (
+                Qpts[:, (j + 2) % 3] * Pke_qpts[i, (j + 1) % 3, :] -
+                Qpts[:, (j + 1) % 3] * Pke_qpts[i, (j + 2) % 3, :]) * Qwts
+            PkCrossXcoeffs[i, j, :] = numpy.dot(Pkp1_at_Qpts, qwts_cur_bf_val)
 #            for k in range( Pkp1.get_num_members() ):
 #                 PkCrossXcoeffs[i,j,k] = sum( Qwts * cur_bf_val * Pkp1_at_Qpts[k,:] )
 #                for l in range( len( Qpts ) ):
@@ -147,19 +144,19 @@ def NedelecSpace3D( ref_el, k ):
 #                                             * cur_bf_val \
 #                                             * Pkp1_at_Qpts[k,l]
 
-    PkCrossX = polynomial_set.PolynomialSet( ref_el, \
-                                             k + 1, \
-                                             k + 1, \
-                                             vec_Pkp1.get_expansion_set(), \
-                                             PkCrossXcoeffs, \
-                                             vec_Pkp1.get_dmats() )
-    return polynomial_set.polynomial_set_union_normalized( vec_Pk, \
-                                                           PkCrossX )
+    PkCrossX = polynomial_set.PolynomialSet(ref_el,
+                                            k + 1,
+                                            k + 1,
+                                            vec_Pkp1.get_expansion_set(),
+                                            PkCrossXcoeffs,
+                                            vec_Pkp1.get_dmats())
+    return polynomial_set.polynomial_set_union_normalized(vec_Pk, PkCrossX)
 
 
-class NedelecDual2D( dual_set.DualSet ):
+class NedelecDual2D(dual_set.DualSet):
     """Dual basis for first-kind Nedelec in 2d """
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         sd = ref_el.get_spatial_dimension()
         if sd != 2:
             raise Exception("Nedelec2D only works on triangles")
@@ -168,61 +165,59 @@ class NedelecDual2D( dual_set.DualSet ):
 
         t = ref_el.get_topology()
 
-        num_edges = len( t[1] )
+        num_edges = len(t[1])
 
         # edge tangents
-        for i in range( num_edges ):
-            pts_cur = ref_el.make_points( 1, i, degree + 2 )
-            for j in range( len( pts_cur ) ):
+        for i in range(num_edges):
+            pts_cur = ref_el.make_points(1, i, degree + 2)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointEdgeTangentEvaluation( ref_el, \
-                                                           i, pt_cur )
-                nodes.append( f )
+                f = functional.PointEdgeTangentEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # internal moments
         if degree > 0:
-            Q = quadrature.make_quadrature( ref_el, 2 * ( degree + 1 ) )
+            Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1))
             qpts = Q.get_points()
-            Pkm1 = polynomial_set.ONPolynomialSet( ref_el, degree - 1 )
-            zero_index = tuple( [ 0 for i in range( sd ) ]  )
-            Pkm1_at_qpts = Pkm1.tabulate( qpts )[ zero_index ]
-
-            for d in range( sd ):
-                for i in range( Pkm1_at_qpts.shape[0] ):
-                    phi_cur = Pkm1_at_qpts[i,:]
-                    l_cur = functional.IntegralMoment( ref_el, Q, \
-                                                       phi_cur, (d,) )
-                    nodes.append( l_cur )
+            Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 1)
+            zero_index = tuple([0 for i in range(sd)])
+            Pkm1_at_qpts = Pkm1.tabulate(qpts)[zero_index]
 
+            for d in range(sd):
+                for i in range(Pkm1_at_qpts.shape[0]):
+                    phi_cur = Pkm1_at_qpts[i, :]
+                    l_cur = functional.IntegralMoment(ref_el, Q, phi_cur, (d,))
+                    nodes.append(l_cur)
 
         entity_ids = {}
 
         # set to empty
-        for i in range( sd + 1 ):
+        for i in range(sd + 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         cur = 0
 
         # edges
-        num_edge_pts = len( ref_el.make_points( 1, 0, degree + 2 ) )
+        num_edge_pts = len(ref_el.make_points(1, 0, degree + 2))
 
-        for i in range( len( t[1] ) ):
-            entity_ids[1][i] = list(range( cur, cur + num_edge_pts))
+        for i in range(len(t[1])):
+            entity_ids[1][i] = list(range(cur, cur + num_edge_pts))
             cur += num_edge_pts
 
         # moments against P_{degree-1} internally, if degree > 0
         if degree > 0:
             num_internal_dof = sd * Pkm1_at_qpts.shape[0]
-            entity_ids[2][0] = list(range( cur, cur + num_internal_dof))
+            entity_ids[2][0] = list(range(cur, cur + num_internal_dof))
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(NedelecDual2D, self).__init__(nodes, ref_el, entity_ids)
 
 
-class NedelecDual3D( dual_set.DualSet ):
+class NedelecDual3D(dual_set.DualSet):
     """Dual basis for first-kind Nedelec in 3d """
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         sd = ref_el.get_spatial_dimension()
         if sd != 3:
             raise Exception("NedelecDual3D only works on tetrahedra")
@@ -232,100 +227,83 @@ class NedelecDual3D( dual_set.DualSet ):
         t = ref_el.get_topology()
 
         # how many edges
-        num_edges = len( t[1] )
+        num_edges = len(t[1])
 
-        for i in range( num_edges ):
+        for i in range(num_edges):
             # points to specify P_k on each edge
-            pts_cur = ref_el.make_points( 1, i, degree + 2 )
-            for j in range( len( pts_cur ) ):
+            pts_cur = ref_el.make_points(1, i, degree + 2)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointEdgeTangentEvaluation( ref_el, \
-                                                           i, pt_cur )
-                nodes.append( f )
-
-        if degree > 0: # face tangents
-            num_faces = len( t[2] )
-            for i in range( num_faces ): # loop over faces
-                pts_cur = ref_el.make_points( 2, i, degree + 2 )
-                for j in range( len( pts_cur ) ): # loop over points
+                f = functional.PointEdgeTangentEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
+
+        if degree > 0:  # face tangents
+            num_faces = len(t[2])
+            for i in range(num_faces):  # loop over faces
+                pts_cur = ref_el.make_points(2, i, degree + 2)
+                for j in range(len(pts_cur)):  # loop over points
                     pt_cur = pts_cur[j]
-                    for k in range(2): # loop over tangents
-                        f = functional.PointFaceTangentEvaluation( ref_el, \
-                                                                   i, k, \
-                                                                   pt_cur )
-                        nodes.append( f )
-
-        if degree > 1: # internal moments
-            Q = quadrature.make_quadrature( ref_el, 2 * ( degree + 1 ) )
-            qpts = Q.get_points()
-            Pkm2 = polynomial_set.ONPolynomialSet( ref_el, degree - 2 )
-            zero_index = tuple( [ 0 for i in range( sd ) ] )
-            Pkm2_at_qpts = Pkm2.tabulate( qpts )[ zero_index ]
+                    for k in range(2):  # loop over tangents
+                        f = functional.PointFaceTangentEvaluation(ref_el, i, k, pt_cur)
+                        nodes.append(f)
 
-            for d in range( sd ):
-                for i in range( Pkm2_at_qpts.shape[0] ):
-                    phi_cur = Pkm2_at_qpts[i,:]
-                    f = functional.IntegralMoment( ref_el, Q, \
-                                                   phi_cur, (d,) )
-                    nodes.append( f )
+        if degree > 1:  # internal moments
+            Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1))
+            qpts = Q.get_points()
+            Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 2)
+            zero_index = tuple([0 for i in range(sd)])
+            Pkm2_at_qpts = Pkm2.tabulate(qpts)[zero_index]
 
+            for d in range(sd):
+                for i in range(Pkm2_at_qpts.shape[0]):
+                    phi_cur = Pkm2_at_qpts[i, :]
+                    f = functional.IntegralMoment(ref_el, Q, phi_cur, (d,))
+                    nodes.append(f)
 
         entity_ids = {}
         # set to empty
-        for i in range( sd + 1 ):
+        for i in range(sd + 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         cur = 0
 
         # edge dof
-        num_pts_per_edge = len( ref_el.make_points( 1, 0, degree + 2 ) )
-        for i in range( len( t[1] ) ):
-            entity_ids[1][i] = list(range( cur, cur + num_pts_per_edge))
+        num_pts_per_edge = len(ref_el.make_points(1, 0, degree + 2))
+        for i in range(len(t[1])):
+            entity_ids[1][i] = list(range(cur, cur + num_pts_per_edge))
             cur += num_pts_per_edge
 
         # face dof
         if degree > 0:
-            num_pts_per_face = len( ref_el.make_points( 2, 0, degree + 2 ) )
-            for i in range( len( t[2] ) ):
-                entity_ids[2][i] = list(range( cur, cur + 2 * num_pts_per_face))
+            num_pts_per_face = len(ref_el.make_points(2, 0, degree + 2))
+            for i in range(len(t[2])):
+                entity_ids[2][i] = list(range(cur, cur + 2 * num_pts_per_face))
                 cur += 2 * num_pts_per_face
 
         if degree > 1:
             num_internal_dof = Pkm2_at_qpts.shape[0] * sd
-            entity_ids[3][0] = list(range( cur, cur + num_internal_dof))
+            entity_ids[3][0] = list(range(cur, cur + num_internal_dof))
 
+        super(NedelecDual3D, self).__init__(nodes, ref_el, entity_ids)
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
 
-class Nedelec( finite_element.FiniteElement ):
+class Nedelec(finite_element.CiarletElement):
     """Nedelec finite element"""
-    def __init__( self, ref_el, q ):
+
+    def __init__(self, ref_el, q):
 
         degree = q - 1
 
         if ref_el.get_spatial_dimension() == 3:
-            poly_set = NedelecSpace3D( ref_el, degree )
-            dual = NedelecDual3D( ref_el, degree )
+            poly_set = NedelecSpace3D(ref_el, degree)
+            dual = NedelecDual3D(ref_el, degree)
         elif ref_el.get_spatial_dimension() == 2:
-            poly_set = NedelecSpace2D( ref_el, degree )
-            dual = NedelecDual2D( ref_el, degree)
+            poly_set = NedelecSpace2D(ref_el, degree)
+            dual = NedelecDual2D(ref_el, degree)
         else:
             raise Exception("Not implemented")
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree,
-                                               mapping="covariant piola")
-
-if __name__ == "__main__":
-    from . import reference_element
-    T = reference_element.DefaultTriangle( )
-    sd = T.get_spatial_dimension()
-
-    for k in range( 1 ):
-        N = Nedelec( T, k )
-        Nfs = N.get_nodal_basis()
-        pts = T.make_lattice( 1 )
-        vals = Nfs.tabulate( pts, 1 )
-        for foo in sorted( vals ):
-            print(foo)
-            print(vals[foo])
+        formdegree = 1  # 1-form
+        super(Nedelec, self).__init__(poly_set, dual, degree, formdegree,
+                                      mapping="covariant piola")
diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py
index d2e5650..905c77e 100644
--- a/FIAT/nedelec_second_kind.py
+++ b/FIAT/nedelec_second_kind.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2010-2012 Marie E. Rognes
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,16 +16,19 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import, print_function, division
+
 import numpy
 
-from .finite_element import FiniteElement
-from .dual_set import DualSet
-from .polynomial_set import ONPolynomialSet
-from .functional import PointEdgeTangentEvaluation as Tangent
-from .functional import FrobeniusIntegralMoment as IntegralMoment
-from .raviart_thomas import RaviartThomas
-from .quadrature import make_quadrature, UFCTetrahedronFaceQuadratureRule
-from .reference_element import UFCTriangle, UFCTetrahedron
+from FIAT.finite_element import CiarletElement
+from FIAT.dual_set import DualSet
+from FIAT.polynomial_set import ONPolynomialSet
+from FIAT.functional import PointEdgeTangentEvaluation as Tangent
+from FIAT.functional import FrobeniusIntegralMoment as IntegralMoment
+from FIAT.raviart_thomas import RaviartThomas
+from FIAT.quadrature import make_quadrature, UFCTetrahedronFaceQuadratureRule
+from FIAT.reference_element import UFCTetrahedron
+
 
 class NedelecSecondKindDual(DualSet):
     """
@@ -55,13 +59,13 @@ class NedelecSecondKindDual(DualSet):
     these elements coincide with the CG_k elements.)
     """
 
-    def __init__ (self, cell, degree):
+    def __init__(self, cell, degree):
 
         # Define degrees of freedom
         (dofs, ids) = self.generate_degrees_of_freedom(cell, degree)
 
         # Call init of super-class
-        DualSet.__init__(self, dofs, cell, ids)
+        super(NedelecSecondKindDual, self).__init__(dofs, cell, ids)
 
     def generate_degrees_of_freedom(self, cell, degree):
         "Generate dofs and geometry-to-dof maps (ids)."
@@ -72,10 +76,9 @@ class NedelecSecondKindDual(DualSet):
         # Extract spatial dimension and topology
         d = cell.get_spatial_dimension()
         assert (d in (2, 3)), "Second kind Nedelecs only implemented in 2/3D."
-        topology = cell.get_topology()
 
         # Zero vertex-based degrees of freedom (d+1 of these)
-        ids[0] = dict(list(zip(list(range(d+1)), ([] for i in range(d+1)))))
+        ids[0] = dict(list(zip(list(range(d + 1)), ([] for i in range(d + 1)))))
 
         # (d+1) degrees of freedom per entity of codimension 1 (edges)
         (edge_dofs, edge_ids) = self._generate_edge_dofs(cell, degree, 0)
@@ -113,7 +116,7 @@ class NedelecSecondKindDual(DualSet):
             dofs += [Tangent(cell, edge, point) for point in points]
 
             # Associate these dofs with this edge
-            i = len(points)*edge
+            i = len(points) * edge
             ids[edge] = list(range(offset + i, offset + i + len(points)))
 
         return (dofs, ids)
@@ -131,16 +134,15 @@ class NedelecSecondKindDual(DualSet):
             return (dofs, ids)
 
         msg = "2nd kind Nedelec face dofs only available with UFC convention"
-        assert isinstance(cell, UFCTetrahedron),  msg
+        assert isinstance(cell, UFCTetrahedron), msg
 
         # Iterate over the faces of the tet
         num_faces = len(cell.get_topology()[2])
         for face in range(num_faces):
 
             # Construct quadrature scheme for this face
-            m = 2*(degree + 1)
+            m = 2 * (degree + 1)
             Q_face = UFCTetrahedronFaceQuadratureRule(face, m)
-            quad_points = Q_face.get_points()
 
             # Construct Raviart-Thomas of (degree - 1) on the
             # reference face
@@ -160,11 +162,11 @@ class NedelecSecondKindDual(DualSet):
 
             # Map Phis -> phis (reference values to physical values)
             J = Q_face.jacobian()
-            scale = 1.0/numpy.sqrt(numpy.linalg.det(J.transpose()*J))
+            scale = 1.0 / numpy.sqrt(numpy.linalg.det(J.transpose() * J))
             phis = numpy.ndarray((d, num_quad_points))
             for i in range(num_rts):
                 for q in range(num_quad_points):
-                    phi_i_q = scale*J*numpy.matrix(Phis[i,:, q]).transpose()
+                    phi_i_q = scale * J * numpy.matrix(Phis[i, :, q]).transpose()
                     for j in range(d):
                         phis[j, q] = phi_i_q[j]
 
@@ -175,7 +177,7 @@ class NedelecSecondKindDual(DualSet):
                 dofs += [IntegralMoment(cell, Q_face, phis)]
 
             # Assign identifiers (num RTs per face + previous edge dofs)
-            ids[face] = list(range(offset + num_rts*face, offset + num_rts*(face+1)))
+            ids[face] = list(range(offset + num_rts*face, offset + num_rts*(face + 1)))
 
         return (dofs, ids)
 
@@ -189,7 +191,7 @@ class NedelecSecondKindDual(DualSet):
             return ([], {0: []})
 
         # Create quadrature points
-        Q = make_quadrature(cell, 2*(degree+1))
+        Q = make_quadrature(cell, 2 * (degree + 1))
         qs = Q.get_points()
 
         # Create Raviart-Thomas nodal basis
@@ -197,17 +199,18 @@ class NedelecSecondKindDual(DualSet):
         phi = RT.get_nodal_basis()
 
         # Evaluate Raviart-Thomas basis at quadrature points
-        phi_at_qs = phi.tabulate(qs)[(0,)*d]
+        phi_at_qs = phi.tabulate(qs)[(0,) * d]
 
         # Use (Frobenius) integral moments against RTs as dofs
-        dofs = [IntegralMoment(cell, Q, phi_at_qs[i,:])
+        dofs = [IntegralMoment(cell, Q, phi_at_qs[i, :])
                 for i in range(len(phi_at_qs))]
 
         # Associate these dofs with the interior
         ids = {0: list(range(offset, offset + len(dofs)))}
         return (dofs, ids)
 
-class NedelecSecondKind(FiniteElement):
+
+class NedelecSecondKind(CiarletElement):
     """
     The H(curl) Nedelec elements of the second kind on triangles and
     tetrahedra: the polynomial space described by the full polynomials
@@ -218,7 +221,7 @@ class NedelecSecondKind(FiniteElement):
     def __init__(self, cell, degree):
 
         # Check degree
-        assert(degree >= 1), "Second kind Nedelecs start at 1!"
+        assert degree >= 1, "Second kind Nedelecs start at 1!"
 
         # Get dimension
         d = cell.get_spatial_dimension()
@@ -229,23 +232,11 @@ class NedelecSecondKind(FiniteElement):
         # Construct dual space
         Ls = NedelecSecondKindDual(cell, degree)
 
+        # Set form degree
+        formdegree = 1  # 1-form
+
         # Set mapping
         mapping = "covariant piola"
 
         # Call init of super-class
-        FiniteElement.__init__(self, Ps, Ls, degree, mapping=mapping)
-
-
-if __name__=="__main__":
-
-    for k in range(1, 4):
-        T = UFCTriangle()
-        N2curl = NedelecSecondKind(T, k)
-
-    for k in range(1, 4):
-        T = UFCTetrahedron()
-        N2curl = NedelecSecondKind(T, k)
-        Nfs = N2curl.get_nodal_basis()
-        pts = T.make_lattice( 1 )
-        vals = Nfs.tabulate( pts, 1 )
-
+        super(NedelecSecondKind, self).__init__(Ps, Ls, degree, formdegree, mapping=mapping)
diff --git a/FIAT/newdubiner.py b/FIAT/newdubiner.py
deleted file mode 100644
index bb46477..0000000
--- a/FIAT/newdubiner.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-import numpy
-
-
-def jrc(a, b, n, num_type):
-    an = num_type((2*n+1+a+b)*(2*n+2+a+b)) \
-        / num_type(2*(n+1)*(n+1+a+b))
-    bn = num_type((a*a-b*b) * (2*n+1+a+b)) \
-        / num_type(2*(n+1)*(2*n+a+b)*(n+1+a+b))
-    cn = num_type((n+a)*(n+b)*(2*n+2+a+b)) \
-        / num_type((n+1)*(n+1+a+b)*(2*n+a+b))
-    return an, bn, cn
-
-
-def lattice_iter(start, finish, depth):
-    """Generator iterating over the depth-dimensional lattice of
-    integers between start and (finish-1).  This works on simplices in
-    1d, 2d, 3d, and beyond"""
-    if depth == 0:
-        return
-    elif depth == 1:
-        for ii in range(start, finish):
-            yield [ii]
-    else:
-        for ii in range(start, finish):
-            for jj in lattice_iter(start, finish-ii, depth - 1):
-                yield [ii] + jj
-
-
-def make_lattice(n, vs, numtype):
-    hs = numpy.array([(vs[i] - vs[0]) / numtype(n)
-                     for i in range(1, len(vs))]
-                     )
-
-    result = []
-
-    m = len(hs)
-    for indices in lattice_iter(0, n+1, m):
-        res_cur = vs[0].copy()
-        for i in range(len(indices)):
-            res_cur += indices[i] * hs[m-i-1]
-        result.append(res_cur)
-
-    return numpy.array(result)
-
-
-def make_triangle_lattice(n, numtype):
-    vs = numpy.array([(numtype(-1), numtype(-1)),
-                      (numtype(1), numtype(-1)),
-                      (numtype(-1), numtype(1))])
-
-    return make_lattice(n, vs, numtype)
-
-
-def make_tetrahedron_lattice(n, numtype):
-    vs = numpy.array([(numtype(-1), numtype(-1), numtype(-1)),
-                      (numtype(1),  numtype(-1), numtype(-1)),
-                      (numtype(-1), numtype(1),  numtype(-1)),
-                      (numtype(-1), numtype(-1), numtype(1))
-                      ])
-    return make_lattice(n, vs, numtype)
-
-
-def make_lattice_dim(D, n, numtype):
-    if D == 2:
-        return make_triangle_lattice(n, numtype)
-    elif D == 3:
-        return make_tetrahedron_lattice(n, numtype)
-
-
-def tabulate_triangle(n, pts, numtype):
-    return _tabulate_triangle_single(n, numpy.array(pts).T, numtype)
-
-
-def _tabulate_triangle_single(n, pts, numtype):
-    if len(pts) == 0:
-        return numpy.array([], numtype)
-
-    def idx(p, q):
-        return (p+q)*(p+q+1)//2 + q
-
-    results = (n+1)*(n+2)//2 * [None]
-
-    results[0] = numtype(1) \
-        + pts[0] - pts[0] \
-        + pts[1] - pts[1]
-
-    if n == 0:
-        return results
-
-    x = pts[0]
-    y = pts[1]
-
-    one = numtype(1)
-    two = numtype(2)
-    three = numtype(3)
-
-    # foo = one + two*x + y
-
-    f1 = (one+two*x+y)/two
-    f2 = (one - y) / two
-    f3 = f2**2
-
-    results[idx(1, 0), :] = f1
-
-    for p in range(1, n):
-        a = (two * p + 1) / (1 + p)
-        # b = p / (p + one)
-        results[idx(p+1, 0)] = a * f1 * results[idx(p, 0), :] \
-            - p/(one+p) * f3 * results[idx(p-1, 0), :]
-
-    for p in range(n):
-        results[idx(p, 1)] = (one + two*p+(three+two*p)*y) / two \
-            * results[idx(p, 0)]
-
-    for p in range(n-1):
-        for q in range(1, n-p):
-            (a1, a2, a3) = jrc(2*p+1, 0, q, numtype)
-            results[idx(p, q+1)] = \
-                (a1 * y + a2) * results[idx(p, q)] \
-                - a3 * results[idx(p, q-1)]
-
-    return results
-
-
-def tabulate_tetrahedron(n, pts, numtype):
-    return _tabulate_tetrahedron_single(n, numpy.array(pts).T, numtype)
-
-
-def _tabulate_tetrahedron_single(n, pts, numtype):
-    def idx(p, q, r):
-        return (p+q+r)*(p+q+r+1)*(p+q+r+2)//6 + (q+r)*(q+r+1)//2 + r
-
-    results = (n+1)*(n+2)*(n+3)//6 * [None]
-    results[0] = 1.0 \
-        + pts[0] - pts[0] \
-        + pts[1] - pts[1] \
-        + pts[2] - pts[2]
-
-    if n == 0:
-        return results
-
-    x = pts[0]
-    y = pts[1]
-    z = pts[2]
-
-    one = numtype(1)
-    two = numtype(2)
-    three = numtype(3)
-
-    factor1 = (two + two*x + y + z) / two
-    factor2 = ((y+z)/two)**2
-    factor3 = (one + two * y + z) / two
-    factor4 = (1 - z) / two
-    factor5 = factor4 ** 2
-
-    results[idx(1, 0, 0)] = factor1
-    for p in range(1, n):
-        a1 = (two * p + one) / (p + one)
-        a2 = p / (p + one)
-        results[idx(p+1, 0, 0)] = a1 * factor1 * results[idx(p, 0, 0)] \
-            - a2 * factor2 * results[idx(p-1, 0, 0)]
-
-    for p in range(0, n):
-        results[idx(p, 1, 0)] = results[idx(p, 0, 0)] \
-            * (p * (one + y) + (two + three * y + z) / two)
-
-    for p in range(0, n-1):
-        for q in range(1, n-p):
-            (aq, bq, cq) = jrc(2*p+1, 0, q, numtype)
-            qmcoeff = aq * factor3 + bq * factor4
-            qm1coeff = cq * factor5
-            results[idx(p, q+1, 0)] = qmcoeff * results[idx(p, q, 0)] \
-                - qm1coeff * results[idx(p, q-1, 0)]
-
-    for p in range(n):
-        for q in range(n-p):
-            results[idx(p, q, 1)] = results[idx(p, q, 0)] \
-                * (one + p + q + (two + q + p) * z)
-
-    for p in range(n-1):
-        for q in range(0, n-p-1):
-            for r in range(1, n-p-q):
-                ar, br, cr = jrc(2*p+2*q+2, 0, r, numtype)
-                results[idx(p, q, r+1)] = \
-                    (ar * z + br) * results[idx(p, q, r)] \
-                    - cr * results[idx(p, q, r-1)]
-
-    return results
-
-
-def tabulate_tetrahedron_derivatives(n, pts, numtype):
-    D = 3
-    order = 1
-    return tabulate_jet(D, n, pts, order, numtype)
-
-
-def tabulate(D, n, pts, numtype):
-    return _tabulate_single(D, n, numpy.array(pts).T, numtype)
-
-
-def _tabulate_single(D, n, pts, numtype):
-    if D == 2:
-        return _tabulate_triangle_single(n, pts, numtype)
-    elif D == 3:
-        return _tabulate_tetrahedron_single(n, pts, numtype)
-
-
-def tabulate_jet(D, n, pts, order, numtype):
-    from .expansions import _tabulate_dpts
-
-    # Wrap the tabulator to allow for nondefault numtypes
-    def tabulator_wrap(n, X):
-        return _tabulate_single(D, n, X, numtype)
-
-    data1 = _tabulate_dpts(tabulator_wrap, D, n, order, pts)
-    # Put data in the required data structure, i.e.,
-    # k-tuples which contain the value, and the k-1 derivatives
-    # (gradient, Hessian, ...)
-    m = data1[0].shape[0]
-    n = data1[0].shape[1]
-    data2 = [[tuple([data1[r][i][j] for r in range(order+1)])
-              for j in range(n)]
-             for i in range(m)]
-    return data2
-
-
-if __name__ == "__main__":
-    import gmpy
-
-    latticeK = 2
-    D = 3
-
-    pts = make_tetrahedron_lattice(latticeK, gmpy.mpq)
-
-    vals = tabulate_tetrahedron_derivatives(D, pts, gmpy.mpq)
-
-    print(vals)
diff --git a/FIAT/nodal_enriched.py b/FIAT/nodal_enriched.py
new file mode 100644
index 0000000..9c8d20e
--- /dev/null
+++ b/FIAT/nodal_enriched.py
@@ -0,0 +1,143 @@
+# Copyright (C) 2013 Andrew T. T. McRae, 2015-2016 Jan Blechta
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import numpy as np
+
+from FIAT.polynomial_set import PolynomialSet
+from FIAT.dual_set import DualSet
+from FIAT.finite_element import CiarletElement
+
+__all__ = ['NodalEnrichedElement']
+
+
+class NodalEnrichedElement(CiarletElement):
+    """NodalEnriched element is a direct sum of a sequence of
+    finite elements. Dual basis is reorthogonalized to the
+    primal basis for nodality.
+
+    The following is equivalent:
+        * the constructor is well-defined,
+        * the resulting element is unisolvent and its basis is nodal,
+        * the supplied elements are unisolvent with nodal basis and
+          their primal bases are mutually linearly independent,
+        * the supplied elements are unisolvent with nodal basis and
+          their dual bases are mutually linearly independent.
+    """
+
+    def __init__(self, *elements):
+
+        # Test elements are nodal
+        if not all(e.is_nodal() for e in elements):
+            raise ValueError("Not all elements given for construction "
+                             "of NodalEnrichedElement are nodal")
+
+        # Extract common data
+        ref_el = elements[0].get_reference_element()
+        expansion_set = elements[0].get_nodal_basis().get_expansion_set()
+        degree = min(e.get_nodal_basis().get_degree() for e in elements)
+        embedded_degree = max(e.get_nodal_basis().get_embedded_degree()
+                              for e in elements)
+        order = max(e.get_order() for e in elements)
+        mapping = elements[0].mapping()[0]
+        formdegree = None if any(e.get_formdegree() is None for e in elements) \
+            else max(e.get_formdegree() for e in elements)
+        value_shape = elements[0].value_shape()
+
+        # Sanity check
+        assert all(e.get_nodal_basis().get_reference_element() ==
+                   ref_el for e in elements)
+        assert all(type(e.get_nodal_basis().get_expansion_set()) ==
+                   type(expansion_set) for e in elements)
+        assert all(e_mapping == mapping for e in elements
+                   for e_mapping in e.mapping())
+        assert all(e.value_shape() == value_shape for e in elements)
+
+        # Merge polynomial sets
+        coeffs = _merge_coeffs([e.get_coeffs() for e in elements])
+        dmats = _merge_dmats([e.dmats() for e in elements])
+        poly_set = PolynomialSet(ref_el,
+                                 degree,
+                                 embedded_degree,
+                                 expansion_set,
+                                 coeffs,
+                                 dmats)
+
+        # Renumber dof numbers
+        offsets = np.cumsum([0] + [e.space_dimension() for e in elements[:-1]])
+        entity_ids = _merge_entity_ids((e.entity_dofs() for e in elements),
+                                       offsets)
+
+        # Merge dual bases
+        nodes = [node for e in elements for node in e.dual_basis()]
+        dual_set = DualSet(nodes, ref_el, entity_ids)
+
+        # CiarletElement constructor adjusts poly_set coefficients s.t.
+        # dual_set is really dual to poly_set
+        super(NodalEnrichedElement, self).__init__(poly_set, dual_set, order,
+                                                   formdegree=formdegree, mapping=mapping)
+
+
+def _merge_coeffs(coeffss):
+    # Number of bases members
+    total_dim = sum(c.shape[0] for c in coeffss)
+
+    # Value shape
+    value_shape = coeffss[0].shape[1:-1]
+    assert all(c.shape[1:-1] == value_shape for c in coeffss)
+
+    # Number of expansion polynomials
+    max_expansion_dim = max(c.shape[-1] for c in coeffss)
+
+    # Compose new coeffs
+    shape = (total_dim,) + value_shape + (max_expansion_dim,)
+    new_coeffs = np.zeros(shape, dtype=coeffss[0].dtype)
+    counter = 0
+    for c in coeffss:
+        dim = c.shape[0]
+        expansion_dim = c.shape[-1]
+        new_coeffs[counter:counter+dim, ..., :expansion_dim] = c
+        counter += dim
+    assert counter == total_dim
+    return new_coeffs
+
+
+def _merge_dmats(dmatss):
+    shape, arg = max((dmats[0].shape, args) for args, dmats in enumerate(dmatss))
+    assert len(shape) == 2 and shape[0] == shape[1]
+    new_dmats = []
+    for dim in range(len(dmatss[arg])):
+        new_dmats.append(dmatss[arg][dim].copy())
+        for dmats in dmatss:
+            sl = slice(0, dmats[dim].shape[0]), slice(0, dmats[dim].shape[1])
+            assert np.allclose(dmats[dim], new_dmats[dim][sl]), \
+                "dmats of elements to be directly summed are not matching!"
+    return new_dmats
+
+
+def _merge_entity_ids(entity_ids, offsets):
+    ret = {}
+    for i, ids in enumerate(entity_ids):
+        for dim in ids:
+            if not ret.get(dim):
+                ret[dim] = {}
+            for entity in ids[dim]:
+                if not ret[dim].get(entity):
+                    ret[dim][entity] = []
+                ret[dim][entity] += (np.array(ids[dim][entity]) + offsets[i]).tolist()
+    return ret
diff --git a/FIAT/orthopoly.py b/FIAT/orthopoly.py
new file mode 100644
index 0000000..fe7ab0a
--- /dev/null
+++ b/FIAT/orthopoly.py
@@ -0,0 +1,384 @@
+"""
+    orthopoly.py - A suite of functions for generating orthogonal polynomials
+    and quadrature rules.
+
+    Copyright (c) 2014 Greg von Winckel
+    All rights reserved.
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Last updated on Wed Jan  1 14:29:25 MST 2014
+
+    Modified by David A. Ham (david.ham at imperial.ac.uk), 2016
+"""
+
+from __future__ import absolute_import, print_function, division
+
+import numpy as np
+from six.moves import xrange, reduce
+from math import gamma
+
+
+def gauss(alpha, beta):
+    """
+    Compute the Gauss nodes and weights from the recursion
+    coefficients associated with a set of orthogonal polynomials
+
+    Inputs:
+    alpha - recursion coefficients
+    beta - recursion coefficients
+
+    Outputs:
+    x - quadrature nodes
+    w - quadrature weights
+
+    Adapted from the MATLAB code by Walter Gautschi
+    http://www.cs.purdue.edu/archives/2002/wxg/codes/gauss.m
+    """
+
+    from numpy.linalg import eigh
+
+    A = np.diag(np.sqrt(beta)[1:], 1) + np.diag(alpha)
+    x, V = eigh(A, "U")
+
+    w = beta[0] * np.real(np.power(V[0, :], 2))
+    return x, w
+
+
+def lobatto(alpha, beta, xl1, xl2):
+    """
+        Compute the Lobatto nodes and weights with the preassigned
+        nodea xl1,xl2
+
+        Inputs:
+        alpha - recursion coefficients
+        beta - recursion coefficients
+        xl1 - assigned node location
+        xl2 - assigned node location
+
+        Outputs:
+        x - quadrature nodes
+        w - quadrature weights
+
+        Based on the section 7 of the paper
+        "Some modified matrix eigenvalue problems"
+        by Gene Golub, SIAM Review Vol 15, No. 2, April 1973, pp.318--334
+    """
+    from numpy.linalg import solve
+    n = len(alpha) - 1
+    en = np.zeros(n)
+    en[-1] = 1
+    A1 = np.vstack((np.sqrt(beta), alpha - xl1))
+    J1 = np.diag(A1[0, 1:-1], 1) + np.diag(A1[1, 1:]) + np.diag(A1[0, 1:-1], -1)
+    A2 = np.vstack((np.sqrt(beta), alpha - xl2))
+    J2 = np.diag(A2[0, 1:-1], 1) + np.diag(A2[1, 1:]) + np.diag(A2[0, 1:-1], -1)
+    g1 = solve(J1, en)
+    g2 = solve(J2, en)
+    C = np.array(((1, -g1[-1]), (1, -g2[-1])))
+    xl = np.array((xl1, xl2))
+    ab = solve(C, xl)
+
+    alphal = alpha
+    alphal[-1] = ab[0]
+    betal = beta
+    betal[-1] = ab[1]
+    x, w = gauss(alphal, betal)
+    return x, w
+
+
+def rec_jacobi(N, a, b):
+    """
+    Generate the recursion coefficients alpha_k, beta_k
+
+    P_{k+1}(x) = (x-alpha_k)*P_{k}(x) - beta_k P_{k-1}(x)
+
+    for the Jacobi polynomials which are orthogonal on [-1,1]
+    with respect to the weight w(x)=[(1-x)^a]*[(1+x)^b]
+
+    Inputs:
+    N - polynomial order
+    a - weight parameter
+    b - weight parameter
+
+    Outputs:
+    alpha - recursion coefficients
+    beta - recursion coefficients
+
+    Adapted from the MATLAB code by Dirk Laurie and Walter Gautschi
+    http://www.cs.purdue.edu/archives/2002/wxg/codes/r_jacobi.m
+    """
+
+    nu = (b - a) / float(a + b + 2)
+    mu = 2 ** (a + b + 1) * gamma(a + 1) * gamma(b + 1) / gamma(a + b + 2)
+
+    if N == 1:
+        alpha = nu
+        beta = mu
+    else:
+        n = np.arange(1.0, N)
+        nab = 2 * n + a + b
+        alpha = np.hstack((nu, (b ** 2 - a ** 2) / (nab * (nab + 2))))
+        n = n[1:]
+        nab = nab[1:]
+        B1 = 4 * (a + 1) * (b + 1) / float((a + b + 2) ** 2 * (a + b + 3))
+        B = 4 * (n + a) * (n + b) * n * (n + a + b) / \
+            (nab ** 2 * (nab + 1) * (nab - 1))
+        beta = np.hstack((mu, B1, B))
+
+    return alpha, beta
+
+
+def rec_jacobi01(N, a, b):
+    """
+    Generate the recursion coefficients alpha_k, beta_k
+    for the Jacobi polynomials which are orthogonal on [0,1]
+
+    See rec_jacobi for the recursion coefficients on [-1,1]
+
+    Inputs:
+    N - polynomial order
+    a - weight parameter
+    b - weight parameter
+
+    Outputs:
+    alpha - recursion coefficients
+    beta - recursion coefficients
+
+    Adapted from the MATLAB implementation:
+    https://www.cs.purdue.edu/archives/2002/wxg/codes/r_jacobi01.m
+
+    """
+
+    if a <= -1 or b <= -1:
+        raise ValueError('''Jacobi coefficients are defined only
+                            for alpha,beta > -1''')
+
+    if not isinstance(N, int):
+        raise TypeError('N must be an integer')
+
+    if N < 1:
+        raise ValueError('N must be at least 1')
+
+    c, d = rec_jacobi(N, a, b)
+
+    alpha = (1 + c) / 2
+    beta = d / 4
+    beta[0] = d[0] / 2 ** (a + b + 1)
+
+    return alpha, beta
+
+
+def polyval(alpha, beta, x):
+    """
+    Evaluate polynomials on x given the recursion coefficients alpha and beta
+    """
+
+    N = len(alpha)
+    m = len(x)
+    P = np.zeros((m, N + 1))
+
+    P[:, 0] = 1
+    P[:, 1] = (x - alpha[0]) * P[:, 0]
+
+    for k in xrange(1, N):
+        P[:, k + 1] = (x - alpha[k]) * P[:, k] - beta[k] * P[:, k - 1]
+
+    return P
+
+
+def jacobi(N, a, b, x, NOPT=1):
+    """
+    JACOBI computes the Jacobi polynomials which are orthogonal on [-1,1]
+    with respect to the weight w(x)=[(1-x)^a]*[(1+x)^b] and evaluate them
+    on the given grid up to P_N(x). Setting NOPT=2 returns the
+    L2-normalized polynomials
+    """
+
+    m = len(x)
+    P = np.zeros((m, N + 1))
+
+    apb = a + b
+    a1 = a - 1
+    b1 = b - 1
+    c = apb * (a - b)
+
+    P[:, 0] = 1
+
+    if N > 0:
+        P[:, 1] = 0.5 * (a - b + (apb + 2) * x)
+
+    if N > 1:
+        for k in xrange(2, N + 1):
+            k2 = 2 * k
+            g = k2 + apb
+            g1 = g - 1
+            g2 = g - 2
+            d = 2.0 * (k + a1) * (k + b1) * g
+            P[:, k] = (g1 * (c + g2 * g * x) * P[:, k - 1] -
+                       d * P[:, k - 2]) / (k2 * (k + apb) * g2)
+
+    if NOPT == 2:
+        k = np.arange(N + 1)
+        pnorm = 2 ** (apb + 1) * gamma(k + a + 1) * gamma(k + b + 1) / \
+            ((2 * k + a + b + 1) * (gamma(k + 1) * gamma(k + a + b + 1)))
+        P *= 1 / np.sqrt(pnorm)
+    return P
+
+
+def jacobiD(N, a, b, x, NOPT=1):
+    """
+    JACOBID computes the first derivatives of the normalized Jacobi
+    polynomials which are orthogonal on [-1,1] with respect
+    to the weight w(x)=[(1-x)^a]*[(1+x)^b] and evaluate them
+    on the given grid up to P_N(x). Setting NOPT=2 returns
+    the derivatives of the L2-normalized polynomials
+    """
+
+    z = np.zeros((len(x), 1))
+    if N == 0:
+        Px = z
+    else:
+
+        Px = 0.5 * np.hstack((z, jacobi(N - 1, a + 1, b + 1, x, NOPT) *
+                              ((a + b + 2 + np.arange(N)))))
+    return Px
+
+
+def mm_log(N, a):
+    """
+    MM_LOG Modified moments for a logarithmic weight function.
+
+    The call mm=MM_LOG(n,a) computes the first n modified moments of the
+    logarithmic weight function w(t)=t^a log(1/t) on [0,1] relative to
+    shifted Legendre polynomials.
+
+    REFERENCE:  Walter Gautschi,``On the preceding paper `A Legendre
+                polynomial integral' by James L. Blue'',
+                Math. Comp. 33 (1979), 742-743.
+
+    Adapted from the MATLAB implementation:
+    https://www.cs.purdue.edu/archives/2002/wxg/codes/mm_log.m
+    """
+
+    if a <= -1:
+        raise ValueError('Parameter a must be greater than -1')
+
+    prod = lambda z: reduce(lambda x, y: x * y, z, 1)
+
+    mm = np.zeros(N)
+
+    c = 1
+    for n in range(N):
+        if isinstance(a, int) and a < n:
+
+            p = range(n - a, n + a + 2)
+            mm[n] = (-1) ** (n - a) / prod(p)
+            mm[n] *= gamma(a + 1) ** 2
+
+        else:
+            if n == 0:
+                mm[0] = 1 / (a + 1) ** 2
+            else:
+                k = np.arange(1, n + 1)
+                s = 1 / (a + 1 + k) - 1 / (a + 1 - k)
+                p = (a + 1 - k) / (a + 1 + k)
+                mm[n] = (1 / (a + 1) + sum(s)) * prod(p) / (a + 1)
+
+        mm[n] *= c
+        c *= 0.5 * (n + 1) / (2 * n + 1)
+
+    return mm
+
+
+def mod_chebyshev(N, mom, alpham, betam):
+    """
+    Calcuate the recursion coefficients for the orthogonal polynomials
+    which are are orthogonal with respect to a weight function which is
+    represented in terms of its modifed moments which are obtained by
+    integrating the monic polynomials against the weight function.
+
+    REFERENCES:
+
+    John C. Wheeler, "Modified moments and Gaussian quadratures"
+    Rocky Mountain Journal of Mathematics, Vol. 4, Num. 2 (1974), 287--296
+
+    Walter Gautschi, "Orthogonal Polynomials (in Matlab)
+    Journal of Computational and Applied Mathematics, Vol. 178 (2005) 215--234
+
+    Adapted from the MATLAB implementation:
+    https://www.cs.purdue.edu/archives/2002/wxg/codes/chebyshev.m
+    """
+
+    if not isinstance(N, int):
+        raise TypeError('N must be an integer')
+
+    if N < 1:
+        raise ValueError('N must be at least 1')
+
+    N = min(N, int(len(mom) / 2))
+
+    alpha = np.zeros(N)
+    beta = np.zeros(N)
+    normsq = np.zeros(N)
+    sig = np.zeros((N + 1, 2 * N))
+
+    alpha[0] = alpham[0] + mom[1] / mom[0]
+    beta[0] = mom[0]
+
+    sig[1, :] = mom
+
+    for n in range(2, N + 1):
+        for m in range(n - 1, 2 * N - n + 1):
+            sig[n, m] = sig[n - 1, m + 1] - (alpha[n - 2] - alpham[m]) * sig[n - 1, m] - \
+                beta[n - 2] * sig[n - 2, m] + betam[m] * sig[n - 1, m - 1]
+
+        alpha[n - 1] = alpham[n - 1] + sig[n, n] / sig[n, n - 1] - sig[n - 1, n - 1] / \
+            sig[n - 1, n - 2]
+        beta[n - 1] = sig[n, n - 1] / sig[n - 1, n - 2]
+
+    normsq = np.diagonal(sig, -1)
+
+    return alpha, beta, normsq
+
+
+def rec_jaclog(N, a):
+    """
+    Generate the recursion coefficients alpha_k, beta_k
+
+    P_{k+1}(x) = (x-alpha_k)*P_{k}(x) - beta_k P_{k-1}(x)
+
+    for the monic polynomials which are orthogonal on [0,1]
+    with respect to the weight w(x)=x^a*log(1/x)
+
+    Inputs:
+    N - polynomial order
+    a - weight parameter
+
+    Outputs:
+    alpha - recursion coefficients
+    beta - recursion coefficients
+
+    Adated from the MATLAB code:
+    https://www.cs.purdue.edu/archives/2002/wxg/codes/r_jaclog.m
+    """
+    alphaj, betaj = rec_jacobi01(2 * N, 0, 0)
+    mom = mm_log(2 * N, a)
+    alpha, beta, _ = mod_chebyshev(N, mom, alphaj, betaj)
+    return alpha, beta
diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py
index d08d853..ceb4b02 100644
--- a/FIAT/polynomial_set.py
+++ b/FIAT/polynomial_set.py
@@ -26,9 +26,11 @@
 # we have an interface for defining sets of functionals (moments against
 # an entire set of polynomials)
 
-from . import expansions
+from __future__ import absolute_import, print_function, division
+
 import numpy
-from .functional import index_iterator
+from FIAT import expansions
+from FIAT.functional import index_iterator
 
 
 def mis(m, n):
@@ -40,8 +42,7 @@ def mis(m, n):
     else:
         return [tuple([n - i] + list(foo))
                 for i in range(n + 1)
-                for foo in mis(m - 1, i)
-                ]
+                for foo in mis(m - 1, i)]
 
 
 # We order coeffs by C_{i,j,k}
@@ -51,7 +52,7 @@ def mis(m, n):
 # k is the expansion function
 # so if I have all bfs at a given point x in an array bf,
 # then dot(coeffs, bf) gives the array of bfs
-class PolynomialSet:
+class PolynomialSet(object):
     """Implements a set of polynomials as linear combinations of an
     expansion set over a reference element.
     ref_el: the reference element
@@ -65,9 +66,9 @@ class PolynomialSet:
          empty) tuple giving the index for a vector- or tensor-valued
          function.
     """
-    def __init__(self, ref_el, degree, embedded_degree,
-                 expansion_set, coeffs, dmats
-                 ):
+
+    def __init__(self, ref_el, degree, embedded_degree, expansion_set, coeffs,
+                 dmats):
         self.ref_el = ref_el
         self.num_members = coeffs.shape[0]
         self.degree = degree
@@ -75,12 +76,10 @@ class PolynomialSet:
         self.expansion_set = expansion_set
         self.coeffs = coeffs
         self.dmats = dmats
-        return
 
     def tabulate_new(self, pts):
         return numpy.dot(self.coeffs,
-                         self.expansion_set.tabulate(self.embedded_degree,
-                                                     pts))
+                         self.expansion_set.tabulate(self.embedded_degree, pts))
 
     def tabulate(self, pts, jet_order=0):
         """Returns the values of the polynomial set."""
@@ -124,37 +123,17 @@ class PolynomialSet:
     def take(self, items):
         """Extracts subset of polynomials given by items."""
         new_coeffs = numpy.take(self.get_coeffs(), items, 0)
-        return PolynomialSet(self.ref_el,
-                             self.degree, self.embedded_degree,
-                             self.expansion_set, new_coeffs,
-                             self.dmats)
-
-    def to_sympy(self):
-        import sys
-        sys.path.append("..")
-        #import FIAT_S
-        import sympy
-        #syms = FIAT_S.polynomials. \
-        #    make_syms(self.get_reference_element().get_spatial_dimension())
-        #ds_nosub = FIAT_S.polynomials.dubs(self.get_embedded_degree(), syms)
-        T1 = reference_element.DefaultReferenceElement()
-        T2 = self.get_reference_element()
-        A, b = reference_element.make_affine_mapping(
-            T2.get_vertices(),
-            T1.get_vertices()
-            )
-
-        if len(self.coeffs.shape) == 2:
-            return [sympy.Polynomial(
-                sum([self.coeffs[i, j] * ds[j]
-                     for j in range(self.coeffs.shape[1])]))
-                    for i in range(self.coeffs.shape[0])]
+        return PolynomialSet(self.ref_el, self.degree, self.embedded_degree,
+                             self.expansion_set, new_coeffs, self.dmats)
 
 
 class ONPolynomialSet(PolynomialSet):
-    """Constructs an orthonormal basis out of expansion set by having
-    an identity matrix of coefficients.  Can be used to specify ON
-    bases  for vector- and tensor-valued sets as well."""
+    """Constructs an orthonormal basis out of expansion set by having an
+    identity matrix of coefficients.  Can be used to specify ON bases
+    for vector- and tensor-valued sets as well.
+
+    """
+
     def __init__(self, ref_el, degree, shape=tuple()):
 
         if shape == tuple():
@@ -169,9 +148,7 @@ class ONPolynomialSet(PolynomialSet):
         sd = ref_el.get_spatial_dimension()
 
         # set up coefficients
-        coeffs_shape = tuple([num_members]
-                             + list(shape)
-                             + [num_exp_functions])
+        coeffs_shape = tuple([num_members] + list(shape) + [num_exp_functions])
         coeffs = numpy.zeros(coeffs_shape, "d")
 
         # use functional's index_iterator function
@@ -189,42 +166,35 @@ class ONPolynomialSet(PolynomialSet):
 
         # construct dmats
         if degree == 0:
-            dmats = [numpy.array([[0.0]], "d")
-                     for i in range(sd)
-                     ]
+            dmats = [numpy.array([[0.0]], "d") for i in range(sd)]
         else:
             pts = ref_el.make_points(sd, 0, degree + sd + 1)
 
             v = numpy.transpose(expansion_set.tabulate(degree, pts))
             vinv = numpy.linalg.inv(v)
 
-            
             dv = expansion_set.tabulate_derivatives(degree, pts)
             dtildes = [[[a[1][i] for a in dvrow] for dvrow in dv]
-                       for i in range(sd)
-                       ]
+                       for i in range(sd)]
 
             dmats = [numpy.dot(vinv, numpy.transpose(dtilde))
-                     for dtilde in dtildes
-                     ]
+                     for dtilde in dtildes]
 
-        PolynomialSet.__init__(self, ref_el,
-                               degree, embedded_degree,
-                               expansion_set, coeffs, dmats
-                               )
+        PolynomialSet.__init__(self, ref_el, degree, embedded_degree,
+                               expansion_set, coeffs, dmats)
 
 
 def project(f, U, Q):
-    """Computes the expansion coefficients of f in terms of the
-    members of a polynomial set U.  Numerical integration is performed
-    by quadrature rule Q."""
+    """Computes the expansion coefficients of f in terms of the members of
+    a polynomial set U.  Numerical integration is performed by
+    quadrature rule Q.
+
+    """
     pts = Q.get_points()
     wts = Q.get_weights()
     f_at_qps = [f(x) for x in pts]
     U_at_qps = U.tabulate(pts)
-    coeffs = numpy.array([sum(wts * f_at_qps * phi)
-                          for phi in U_at_qps
-                          ])
+    coeffs = numpy.array([sum(wts * f_at_qps * phi) for phi in U_at_qps])
     return coeffs
 
 
@@ -242,7 +212,9 @@ def polynomial_set_union_normalized(A, B):
     """Given polynomial sets A and B, constructs a new polynomial set
     whose span is the same as that of span(A) union span(B).  It may
     not contain any of the same members of the set, as we construct a
-    span via SVD."""
+    span via SVD.
+
+    """
     new_coeffs = numpy.array(list(A.coeffs) + list(B.coeffs))
     func_shape = new_coeffs.shape[1:]
     if len(func_shape) == 1:
@@ -257,25 +229,26 @@ def polynomial_set_union_normalized(A, B):
         (u, sig, vt) = numpy.linalg.svd(nc, 1)
         num_sv = len([s for s in sig if abs(s) > 1.e-10])
 
-        coeffs = numpy.reshape(vt[:num_sv],
-                               tuple([num_sv] + list(func_shape))
-                               )
+        coeffs = numpy.reshape(vt[:num_sv], tuple([num_sv] + list(func_shape)))
 
     return PolynomialSet(A.get_reference_element(),
                          A.get_degree(),
                          A.get_embedded_degree(),
                          A.get_expansion_set(),
-                         coeffs, A.get_dmats())
+                         coeffs,
+                         A.get_dmats())
+
 
 class ONSymTensorPolynomialSet(PolynomialSet):
-    """
-    Constructs an orthonormal basis for symmetric-tensor-valued
+    """Constructs an orthonormal basis for symmetric-tensor-valued
     polynomials on a reference element.
+
     """
-    def __init__(self, ref_el, degree, size = None):
+
+    def __init__(self, ref_el, degree, size=None):
 
         sd = ref_el.get_spatial_dimension()
-        if size == None:
+        if size is None:
             size = sd
 
         shape = (size, size)
@@ -286,9 +259,7 @@ class ONSymTensorPolynomialSet(PolynomialSet):
         expansion_set = expansions.get_expansion_set(ref_el)
 
         # set up coefficients for symmetric tensors
-        coeffs_shape = tuple([num_members]
-                             + list(shape)
-                             + [num_exp_functions])
+        coeffs_shape = tuple([num_members] + list(shape) + [num_exp_functions])
         coeffs = numpy.zeros(coeffs_shape, "d")
         cur_bf = 0
         for [i, j] in index_iterator(shape):
@@ -305,7 +276,7 @@ class ONSymTensorPolynomialSet(PolynomialSet):
                     cur_idx = tuple([cur_bf] + [j, i] + [exp_bf])
                     coeffs[cur_idx] = 1.0
                     cur_bf += 1
-                
+
         # construct dmats. this is the same as ONPolynomialSet.
         pts = ref_el.make_points(sd, 0, degree + sd + 1)
         v = numpy.transpose(expansion_set.tabulate(degree, pts))
@@ -313,29 +284,6 @@ class ONSymTensorPolynomialSet(PolynomialSet):
         dv = expansion_set.tabulate_derivatives(degree, pts)
         dtildes = [[[a[1][i] for a in dvrow] for dvrow in dv]
                    for i in range(sd)]
-        dmats = [numpy.dot(vinv, numpy.transpose(dtilde))
-                 for dtilde in dtildes]
-        PolynomialSet.__init__(self, ref_el,
-                               degree, embedded_degree,
-                               expansion_set, coeffs, dmats
-                               )
-
-
-
-if __name__ == "__main__":
-    from . import reference_element
-
-    T = reference_element.UFCTriangle()
-    U = ONPolynomialSet(T, 2)
-
-    print(U.coeffs[0:6, 0:6])
-
-    pts = T.make_lattice(3)
-
-    jet = U.tabulate(pts, 1)
-    for alpha in sorted(jet):
-        print(alpha)
-        print(jet[alpha])
-
-
-#    print U.get_shape()
+        dmats = [numpy.dot(vinv, numpy.transpose(dtilde)) for dtilde in dtildes]
+        PolynomialSet.__init__(self, ref_el, degree, embedded_degree,
+                               expansion_set, coeffs, dmats)
diff --git a/FIAT/quadrature.py b/FIAT/quadrature.py
index cbec66f..786b388 100644
--- a/FIAT/quadrature.py
+++ b/FIAT/quadrature.py
@@ -16,112 +16,187 @@
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 #
 # Modified by Marie E. Rognes (meg at simula.no), 2012
+# Modified by David A. Ham (david.ham at imperial.ac.uk), 2015
 
-from . import reference_element, expansions, jacobi
+from __future__ import absolute_import, print_function, division
+
+import itertools
 import math
 import numpy
-from .factorial import factorial
 
-class QuadratureRule:
+from FIAT import reference_element, expansions, jacobi, orthopoly
+
+
+class QuadratureRule(object):
     """General class that models integration over a reference element
     as the weighted sum of a function evaluated at a set of points."""
-    def __init__( self, ref_el, pts, wts ):
+
+    def __init__(self, ref_el, pts, wts):
+        if len(wts) != len(pts):
+            raise ValueError("Have %d weights, but %d points" % (len(wts), len(pts)))
+
         self.ref_el = ref_el
         self.pts = pts
         self.wts = wts
-        return
-    def get_points( self ):
+
+    def get_points(self):
         return numpy.array(self.pts)
-    def get_weights( self ):
+
+    def get_weights(self):
         return numpy.array(self.wts)
-    def integrate( self, f ):
-        return sum( [ w * f(x) for (x, w) in zip(self.pts, self.wts) ] )
 
-class GaussJacobiQuadratureLineRule( QuadratureRule ):
+    def integrate(self, f):
+        return sum([w * f(x) for (x, w) in zip(self.pts, self.wts)])
+
+
+class GaussJacobiQuadratureLineRule(QuadratureRule):
     """Gauss-Jacobi quadature rule determined by Jacobi weights a and b
     using m roots of m:th order Jacobi polynomial."""
-#    def __init__( self , ref_el , a , b , m ):
-    def __init__( self, ref_el, m ):
+
+    def __init__(self, ref_el, m):
         # this gives roots on the default (-1,1) reference element
-#        (xs_ref,ws_ref) = compute_gauss_jacobi_rule( a , b , m )
-        (xs_ref, ws_ref) = compute_gauss_jacobi_rule( 0., 0., m )
+        #        (xs_ref, ws_ref) = compute_gauss_jacobi_rule(a, b, m)
+        (xs_ref, ws_ref) = compute_gauss_jacobi_rule(0., 0., m)
+
+        Ref1 = reference_element.DefaultLine()
+        A, b = reference_element.make_affine_mapping(Ref1.get_vertices(),
+                                                     ref_el.get_vertices())
+
+        mapping = lambda x: numpy.dot(A, x) + b
+
+        scale = numpy.linalg.det(A)
+
+        xs = tuple([tuple(mapping(x_ref)[0]) for x_ref in xs_ref])
+        ws = tuple([scale * w for w in ws_ref])
+
+        QuadratureRule.__init__(self, ref_el, xs, ws)
+
+
+class GaussLobattoLegendreQuadratureLineRule(QuadratureRule):
+    """Implement the Gauss-Lobatto-Legendre quadrature rules on the interval using
+    Greg von Winckel's implementation. This facilitates implementing
+    spectral elements.
+
+    The quadrature rule uses m points for a degree of precision of 2m-3.
+    """
+    def __init__(self, ref_el, m):
+        if m < 2:
+            raise ValueError(
+                "Gauss-Labotto-Legendre quadrature invalid for fewer than 2 points")
 
         Ref1 = reference_element.DefaultLine()
-        A, b = reference_element.make_affine_mapping( Ref1.get_vertices(), \
-                                                     ref_el.get_vertices() )
+        verts = Ref1.get_vertices()
+
+        if m > 2:
+            # Calculate the recursion coefficients.
+            alpha, beta = orthopoly.rec_jacobi(m, 0, 0)
+            xs_ref, ws_ref = orthopoly.lobatto(alpha, beta, verts[0][0], verts[1][0])
+        else:
+            # Special case for lowest order.
+            xs_ref = [v[0] for v in verts[:]]
+            ws_ref = (0.5 * (xs_ref[1] - xs_ref[0]), ) * 2
+
+        A, b = reference_element.make_affine_mapping(Ref1.get_vertices(),
+                                                     ref_el.get_vertices())
+
+        mapping = lambda x: numpy.dot(A, x) + b
 
-        mapping = lambda x: numpy.dot( A, x ) + b
+        scale = numpy.linalg.det(A)
 
-        scale = numpy.linalg.det( A )
+        xs = tuple([tuple(mapping(x_ref)[0]) for x_ref in xs_ref])
+        ws = tuple([scale * w for w in ws_ref])
 
-        xs = tuple( [ tuple( mapping( x_ref )[0] ) for x_ref in xs_ref ] )
-        ws = tuple( [ scale * w for w in ws_ref ] )
+        QuadratureRule.__init__(self, ref_el, xs, ws)
 
-        QuadratureRule.__init__( self, ref_el, xs, ws )
 
-        return
+class GaussLegendreQuadratureLineRule(QuadratureRule):
+    """Produce the Gauss--Legendre quadrature rules on the interval using
+    the implementation in numpy. This facilitates implementing
+    discontinuous spectral elements.
 
+    The quadrature rule uses m points for a degree of precision of 2m-1.
+    """
+    def __init__(self, ref_el, m):
+        if m < 1:
+            raise ValueError(
+                "Gauss-Legendre quadrature invalid for fewer than 2 points")
 
-class CollapsedQuadratureTriangleRule( QuadratureRule ):
+        xs_ref, ws_ref = numpy.polynomial.legendre.leggauss(m)
+
+        A, b = reference_element.make_affine_mapping(((-1.,), (1.)),
+                                                     ref_el.get_vertices())
+
+        mapping = lambda x: numpy.dot(A, x) + b
+
+        scale = numpy.linalg.det(A)
+
+        xs = tuple([tuple(mapping(x_ref)[0]) for x_ref in xs_ref])
+        ws = tuple([scale * w for w in ws_ref])
+
+        QuadratureRule.__init__(self, ref_el, xs, ws)
+
+
+class CollapsedQuadratureTriangleRule(QuadratureRule):
     """Implements the collapsed quadrature rules defined in
     Karniadakis & Sherwin by mapping products of Gauss-Jacobi rules
     from the square to the triangle."""
-    def __init__( self, ref_el, m ):
+
+    def __init__(self, ref_el, m):
         ptx, wx = compute_gauss_jacobi_rule(0., 0., m)
         pty, wy = compute_gauss_jacobi_rule(1., 0., m)
 
         # map ptx , pty
-        pts_ref = [ expansions.xi_triangle( (x, y) ) \
-                    for x in ptx for y in pty ]
+        pts_ref = [expansions.xi_triangle((x, y))
+                   for x in ptx for y in pty]
 
         Ref1 = reference_element.DefaultTriangle()
-        A, b = reference_element.make_affine_mapping( Ref1.get_vertices(), \
-                                                     ref_el.get_vertices() )
-        mapping = lambda x: numpy.dot( A, x ) + b
+        A, b = reference_element.make_affine_mapping(Ref1.get_vertices(),
+                                                     ref_el.get_vertices())
+        mapping = lambda x: numpy.dot(A, x) + b
 
-        scale = numpy.linalg.det( A )
+        scale = numpy.linalg.det(A)
 
-        pts = tuple( [ tuple( mapping( x ) ) for x in pts_ref ] )
+        pts = tuple([tuple(mapping(x)) for x in pts_ref])
 
-        wts = [ 0.5 * scale * w1 * w2 for w1 in wx for w2 in wy ]
+        wts = [0.5 * scale * w1 * w2 for w1 in wx for w2 in wy]
 
-        QuadratureRule.__init__( self, ref_el, tuple( pts ), tuple( wts ) )
+        QuadratureRule.__init__(self, ref_el, tuple(pts), tuple(wts))
 
-        return
 
-class CollapsedQuadratureTetrahedronRule( QuadratureRule ):
+class CollapsedQuadratureTetrahedronRule(QuadratureRule):
     """Implements the collapsed quadrature rules defined in
     Karniadakis & Sherwin by mapping products of Gauss-Jacobi rules
     from the cube to the tetrahedron."""
-    def __init__( self, ref_el, m ):
+
+    def __init__(self, ref_el, m):
         ptx, wx = compute_gauss_jacobi_rule(0., 0., m)
         pty, wy = compute_gauss_jacobi_rule(1., 0., m)
         ptz, wz = compute_gauss_jacobi_rule(2., 0., m)
 
         # map ptx , pty
-        pts_ref = [ expansions.xi_tetrahedron( (x, y, z ) ) \
-                    for x in ptx for y in pty for z in ptz ]
+        pts_ref = [expansions.xi_tetrahedron((x, y, z))
+                   for x in ptx for y in pty for z in ptz]
 
         Ref1 = reference_element.DefaultTetrahedron()
-        A, b = reference_element.make_affine_mapping( Ref1.get_vertices(), \
-                                                     ref_el.get_vertices() )
-        mapping = lambda x: numpy.dot( A, x ) + b
+        A, b = reference_element.make_affine_mapping(Ref1.get_vertices(),
+                                                     ref_el.get_vertices())
+        mapping = lambda x: numpy.dot(A, x) + b
 
-        scale = numpy.linalg.det( A )
+        scale = numpy.linalg.det(A)
 
-        pts = tuple( [ tuple( mapping( x ) ) for x in pts_ref ] )
+        pts = tuple([tuple(mapping(x)) for x in pts_ref])
 
-        wts = [ scale * 0.125 * w1 * w2 * w3 \
-                for w1 in wx for w2 in wy for w3 in wz ]
+        wts = [scale * 0.125 * w1 * w2 * w3
+               for w1 in wx for w2 in wy for w3 in wz]
 
-        QuadratureRule.__init__( self, ref_el, tuple( pts ), tuple( wts ) )
+        QuadratureRule.__init__(self, ref_el, tuple(pts), tuple(wts))
 
-        return
 
 class UFCTetrahedronFaceQuadratureRule(QuadratureRule):
     """Highly specialized quadrature rule for the face of a
     tetrahedron, mapped from a reference triangle, used for higher
     order Nedelecs"""
+
     def __init__(self, face_number, degree):
 
         # Create quadrature rule on reference triangle
@@ -146,8 +221,8 @@ class UFCTetrahedronFaceQuadratureRule(QuadratureRule):
         points = numpy.array([F(p) for p in ref_points])
 
         # Map weights: multiply reference weights by sqrt(|J^T J|)
-        detJTJ = numpy.linalg.det(J.transpose()*J)
-        weights = numpy.sqrt(detJTJ)*ref_weights
+        detJTJ = numpy.linalg.det(J.transpose() * J)
+        weights = numpy.sqrt(detJTJ) * ref_weights
 
         # Initialize super class with new points and weights
         QuadratureRule.__init__(self, reference_tet, points, weights)
@@ -161,21 +236,44 @@ class UFCTetrahedronFaceQuadratureRule(QuadratureRule):
         return self._J
 
 
-def make_quadrature( ref_el, m ):
+def make_quadrature(ref_el, m):
     """Returns the collapsed quadrature rule using m points per
-    direction on the given reference element."""
+    direction on the given reference element. In the tensor product
+    case, m is a tuple."""
+
+    if isinstance(m, tuple):
+        min_m = min(m)
+    else:
+        min_m = m
 
-    msg = "Expecting at least one (not %d) quadrature point per direction" % m
-    assert (m > 0), msg
-    if ref_el.get_shape() == reference_element.LINE:
-        return GaussJacobiQuadratureLineRule( ref_el, m )
+    msg = "Expecting at least one (not %d) quadrature point per direction" % min_m
+    assert (min_m > 0), msg
+
+    if ref_el.get_shape() == reference_element.POINT:
+        return QuadratureRule(ref_el, [()], [1])
+    elif ref_el.get_shape() == reference_element.LINE:
+        return GaussJacobiQuadratureLineRule(ref_el, m)
     elif ref_el.get_shape() == reference_element.TRIANGLE:
-        return CollapsedQuadratureTriangleRule( ref_el, m )
+        return CollapsedQuadratureTriangleRule(ref_el, m)
     elif ref_el.get_shape() == reference_element.TETRAHEDRON:
-        return CollapsedQuadratureTetrahedronRule( ref_el, m )
+        return CollapsedQuadratureTetrahedronRule(ref_el, m)
+
+
+def make_tensor_product_quadrature(*quad_rules):
+    """Returns the quadrature rule for a TensorProduct cell, by combining
+    the quadrature rules of the components."""
+    ref_el = reference_element.TensorProductCell(*[q.ref_el
+                                                   for q in quad_rules])
+    # Coordinates are "concatenated", weights are multiplied
+    pts = [list(itertools.chain(*pt_tuple))
+           for pt_tuple in itertools.product(*[q.pts for q in quad_rules])]
+    wts = [numpy.prod(wt_tuple)
+           for wt_tuple in itertools.product(*[q.wts for q in quad_rules])]
+    return QuadratureRule(ref_el, pts, wts)
+
 
 # rule to get Gauss-Jacobi points
-def compute_gauss_jacobi_points( a, b, m ):
+def compute_gauss_jacobi_points(a, b, m):
     """Computes the m roots of P_{m}^{a,b} on [-1,1] by Newton's method.
     The initial guesses are the Chebyshev points.  Algorithm
     implemented in Python from the pseudocode given by Karniadakis and
@@ -184,15 +282,15 @@ def compute_gauss_jacobi_points( a, b, m ):
     eps = 1.e-8
     max_iter = 100
     for k in range(0, m):
-        r = -math.cos(( 2.0*k + 1.0) * math.pi / ( 2.0 * m ) )
+        r = -math.cos((2.0 * k + 1.0) * math.pi / (2.0 * m))
         if k > 0:
-            r = 0.5 * ( r + x[k-1] )
+            r = 0.5 * (r + x[k - 1])
         j = 0
         delta = 2 * eps
         while j < max_iter:
             s = 0
             for i in range(0, k):
-                s = s + 1.0 / ( r - x[i] )
+                s = s + 1.0 / (r - x[i])
             f = jacobi.eval_jacobi(a, b, m, r)
             fp = jacobi.eval_jacobi_deriv(a, b, m, r)
             delta = f / (fp - f * s)
@@ -207,88 +305,18 @@ def compute_gauss_jacobi_points( a, b, m ):
         x.append(r)
     return x
 
-def compute_gauss_jacobi_rule( a, b, m ):
-    xs = compute_gauss_jacobi_points( a, b, m )
 
-    a1 = math.pow(2, a+b+1)
-    a2 = gamma(a + m + 1)
-    a3 = gamma(b + m + 1)
-    a4 = gamma(a + b + m + 1)
-    a5 = factorial(m)
+def compute_gauss_jacobi_rule(a, b, m):
+    xs = compute_gauss_jacobi_points(a, b, m)
+
+    a1 = math.pow(2, a + b + 1)
+    a2 = math.gamma(a + m + 1)
+    a3 = math.gamma(b + m + 1)
+    a4 = math.gamma(a + b + m + 1)
+    a5 = math.factorial(m)
     a6 = a1 * a2 * a3 / a4 / a5
 
-    ws = [ a6 / (1.0 - x**2.0) / jacobi.eval_jacobi_deriv(a, b, m, x)**2.0 \
-           for x in xs ]
+    ws = [a6 / (1.0 - x**2.0) / jacobi.eval_jacobi_deriv(a, b, m, x)**2.0
+          for x in xs]
 
     return xs, ws
-
-
-# A C implementation for ln_gamma function taken from Numerical
-# recipes in C: The art of scientific
-# computing, 2nd edition, Press, Teukolsky, Vetterling, Flannery, Cambridge
-# University press, page 214
-# translated into Python by Robert Kirby
-# See originally Abramowitz and Stegun's Handbook of Mathematical Functions.
-
-def ln_gamma( xx ):
-    cof = [76.18009172947146,\
-           -86.50532032941677, \
-           24.01409824083091, \
-           -1.231739572450155, \
-           0.1208650973866179e-2, \
-           -0.5395239384953e-5 ]
-    y = xx
-    x = xx
-    tmp = x + 5.5
-    tmp -= (x + 0.5) * math.log(tmp)
-    ser = 1.000000000190015
-    for j in range(0, 6):
-        y = y + 1
-        ser += cof[j] / y
-    return -tmp + math.log( 2.5066282746310005*ser/x )
-
-def gamma( xx ):
-    return math.exp( ln_gamma( xx ) )
-
-if __name__ == "__main__":
-    T = reference_element.DefaultTetrahedron()
-    Q = make_quadrature( T, 6 )
-    es = expansions.get_expansion_set( T )
-
-    qpts = Q.get_points()
-    qwts = Q.get_weights()
-
-    phis = es.tabulate( 3, qpts )
-
-    foo = numpy.array( [ [ sum( [ qwts[k] * phis[i, k] * phis[j, k] \
-                                      for k in range( len( qpts ) ) ] )  \
-                           for i in range( phis.shape[0] ) ] \
-                             for j in range( phis.shape[0] ) ] )
-
-#    print qpts
-#    print qwts
-    #print foo
-    cells = [(reference_element.default_simplex(i), reference_element.ufc_simplex(i)) for i in range(1, 4)]
-    order = 1
-    for def_elem, ufc_elem in cells:
-        print("\n\ndefault element")
-        print(def_elem.get_vertices())
-        print("ufc element")
-        print(ufc_elem.get_vertices())
-
-        qd = make_quadrature(def_elem, order)
-        print("\ndefault points:")
-        print(qd.get_points())
-        print("default weights:")
-        print(qd.get_weights())
-        print("sum: ", sum(qd.get_weights()))
-
-        qu = make_quadrature(ufc_elem, order)
-        print("\nufc points:")
-        print(qu.get_points())
-        print("ufc weights:")
-        print(qu.get_weights())
-        print("sum: ", sum(qu.get_weights()))
-
-
-
diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py
new file mode 100644
index 0000000..43469e5
--- /dev/null
+++ b/FIAT/quadrature_schemes.py
@@ -0,0 +1,307 @@
+"""Quadrature schemes on cells
+
+This module generates quadrature schemes on reference cells that integrate
+exactly a polynomial of a given degree using a specified scheme.
+
+Scheme options are:
+
+  scheme="default"
+
+  scheme="canonical" (collapsed Gauss scheme)
+
+Background on the schemes:
+
+  Keast rules for tetrahedra:
+    Keast, P. Moderate-degree tetrahedral quadrature formulas, Computer
+    Methods in Applied Mechanics and Engineering 55(3):339-348, 1986.
+    http://dx.doi.org/10.1016/0045-7825(86)90059-9
+"""
+
+# Copyright (C) 2011 Garth N. Wells
+# Copyright (C) 2016 Miklos Homolya
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# First added:  2011-04-19
+# Last changed: 2011-04-19
+
+from __future__ import absolute_import, print_function, division
+
+# NumPy
+from numpy import array, arange, float64
+
+# FIAT
+from FIAT.reference_element import QUADRILATERAL, TENSORPRODUCT, UFCTriangle, UFCTetrahedron
+from FIAT.quadrature import QuadratureRule, make_quadrature, make_tensor_product_quadrature
+
+
+def create_quadrature(ref_el, degree, scheme="default"):
+    """
+    Generate quadrature rule for given reference element
+    that will integrate an polynomial of order 'degree' exactly.
+
+    For low-degree (<=6) polynomials on triangles and tetrahedra, this
+    uses hard-coded rules, otherwise it falls back to a collapsed
+    Gauss scheme on simplices.  On tensor-product cells, it is a
+    tensor-product quadrature rule of the subcells.
+
+    :arg cell: The FIAT cell to create the quadrature for.
+    :arg degree: The degree of polynomial that the rule should
+        integrate exactly.
+    """
+    if ref_el.get_shape() == TENSORPRODUCT:
+        try:
+            degree = tuple(degree)
+        except TypeError:
+            degree = (degree,) * len(ref_el.cells)
+
+        assert len(ref_el.cells) == len(degree)
+        quad_rules = [create_quadrature(c, d, scheme)
+                      for c, d in zip(ref_el.cells, degree)]
+        return make_tensor_product_quadrature(*quad_rules)
+
+    if ref_el.get_shape() == QUADRILATERAL:
+        return create_quadrature(ref_el.product, degree, scheme)
+
+    if degree < 0:
+        raise ValueError("Need positive degree, not %d" % degree)
+
+    if scheme == "default":
+        # TODO: Point transformation to support good schemes on
+        # non-UFC reference elements.
+        if isinstance(ref_el, UFCTriangle):
+            return _triangle_scheme(degree)
+        elif isinstance(ref_el, UFCTetrahedron):
+            return _tetrahedron_scheme(degree)
+        else:
+            return _fiat_scheme(ref_el, degree)
+    elif scheme == "canonical":
+        return _fiat_scheme(ref_el, degree)
+    else:
+        raise ValueError("Unknown quadrature scheme: %s." % scheme)
+
+
+def _fiat_scheme(ref_el, degree):
+    """Get quadrature scheme from FIAT interface"""
+
+    # Number of points per axis for exact integration
+    num_points_per_axis = (degree + 1 + 1) // 2
+
+    # Check for excess
+    if num_points_per_axis > 30:
+        dim = ref_el.get_spatial_dimension()
+        raise RuntimeError("Requested a quadrature rule with %d points per direction (%d points)" %
+                           (num_points_per_axis, num_points_per_axis**dim))
+
+    # Create and return FIAT quadrature rule
+    return make_quadrature(ref_el, num_points_per_axis)
+
+
+def _triangle_scheme(degree):
+    """Return a quadrature scheme on a triangle of specified order. Falls
+    back on canonical rule for higher orders."""
+
+    if degree == 0 or degree == 1:
+        # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1
+        x = array([[1.0/3.0, 1.0/3.0]])
+        w = array([0.5])
+    elif degree == 2:
+        # Scheme from Strang and Fix, 3 points, degree of precision 2
+        x = array([[1.0/6.0, 1.0/6.0],
+                   [1.0/6.0, 2.0/3.0],
+                   [2.0/3.0, 1.0/6.0]])
+        w = arange(3, dtype=float64)
+        w[:] = 1.0/6.0
+    elif degree == 3:
+        # Scheme from Strang and Fix, 6 points, degree of precision 3
+        x = array([[0.659027622374092, 0.231933368553031],
+                   [0.659027622374092, 0.109039009072877],
+                   [0.231933368553031, 0.659027622374092],
+                   [0.231933368553031, 0.109039009072877],
+                   [0.109039009072877, 0.659027622374092],
+                   [0.109039009072877, 0.231933368553031]])
+        w = arange(6, dtype=float64)
+        w[:] = 1.0/12.0
+    elif degree == 4:
+        # Scheme from Strang and Fix, 6 points, degree of precision 4
+        x = array([[0.816847572980459, 0.091576213509771],
+                   [0.091576213509771, 0.816847572980459],
+                   [0.091576213509771, 0.091576213509771],
+                   [0.108103018168070, 0.445948490915965],
+                   [0.445948490915965, 0.108103018168070],
+                   [0.445948490915965, 0.445948490915965]])
+        w = arange(6, dtype=float64)
+        w[0:3] = 0.109951743655322
+        w[3:6] = 0.223381589678011
+        w = w/2.0
+    elif degree == 5:
+        # Scheme from Strang and Fix, 7 points, degree of precision 5
+        x = array([[0.33333333333333333, 0.33333333333333333],
+                   [0.79742698535308720, 0.10128650732345633],
+                   [0.10128650732345633, 0.79742698535308720],
+                   [0.10128650732345633, 0.10128650732345633],
+                   [0.05971587178976981, 0.47014206410511505],
+                   [0.47014206410511505, 0.05971587178976981],
+                   [0.47014206410511505, 0.47014206410511505]])
+        w = arange(7, dtype=float64)
+        w[0] = 0.22500000000000000
+        w[1:4] = 0.12593918054482717
+        w[4:7] = 0.13239415278850616
+        w = w/2.0
+    elif degree == 6:
+        # Scheme from Strang and Fix, 12 points, degree of precision 6
+        x = array([[0.873821971016996, 0.063089014491502],
+                   [0.063089014491502, 0.873821971016996],
+                   [0.063089014491502, 0.063089014491502],
+                   [0.501426509658179, 0.249286745170910],
+                   [0.249286745170910, 0.501426509658179],
+                   [0.249286745170910, 0.249286745170910],
+                   [0.636502499121399, 0.310352451033785],
+                   [0.636502499121399, 0.053145049844816],
+                   [0.310352451033785, 0.636502499121399],
+                   [0.310352451033785, 0.053145049844816],
+                   [0.053145049844816, 0.636502499121399],
+                   [0.053145049844816, 0.310352451033785]])
+        w = arange(12, dtype=float64)
+        w[0:3] = 0.050844906370207
+        w[3:6] = 0.116786275726379
+        w[6:12] = 0.082851075618374
+        w = w/2.0
+    else:
+        # Get canonical scheme
+        return _fiat_scheme(UFCTriangle(), degree)
+
+    # Return scheme
+    return QuadratureRule(UFCTriangle(), x, w)
+
+
+def _tetrahedron_scheme(degree):
+    """Return a quadrature scheme on a tetrahedron of specified
+    degree. Falls back on canonical rule for higher orders"""
+
+    if degree == 0 or degree == 1:
+        # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1
+        x = array([[1.0/4.0, 1.0/4.0, 1.0/4.0]])
+        w = array([1.0/6.0])
+    elif degree == 2:
+        # Scheme from Zienkiewicz and Taylor, 4 points, degree of precision 2
+        a, b = 0.585410196624969, 0.138196601125011
+        x = array([[a, b, b],
+                   [b, a, b],
+                   [b, b, a],
+                   [b, b, b]])
+        w = arange(4, dtype=float64)
+        w[:] = 1.0/24.0
+    elif degree == 3:
+        # Scheme from Zienkiewicz and Taylor, 5 points, degree of precision 3
+        # Note: this scheme has a negative weight
+        x = array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000],
+                   [0.5000000000000000, 0.1666666666666666, 0.1666666666666666],
+                   [0.1666666666666666, 0.5000000000000000, 0.1666666666666666],
+                   [0.1666666666666666, 0.1666666666666666, 0.5000000000000000],
+                   [0.1666666666666666, 0.1666666666666666, 0.1666666666666666]])
+        w = arange(5, dtype=float64)
+        w[0] = -0.8
+        w[1:5] = 0.45
+        w = w/6.0
+    elif degree == 4:
+        # Keast rule, 14 points, degree of precision 4
+        # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html
+        # (KEAST5)
+        x = array([[0.0000000000000000, 0.5000000000000000, 0.5000000000000000],
+                   [0.5000000000000000, 0.0000000000000000, 0.5000000000000000],
+                   [0.5000000000000000, 0.5000000000000000, 0.0000000000000000],
+                   [0.5000000000000000, 0.0000000000000000, 0.0000000000000000],
+                   [0.0000000000000000, 0.5000000000000000, 0.0000000000000000],
+                   [0.0000000000000000, 0.0000000000000000, 0.5000000000000000],
+                   [0.6984197043243866, 0.1005267652252045, 0.1005267652252045],
+                   [0.1005267652252045, 0.1005267652252045, 0.1005267652252045],
+                   [0.1005267652252045, 0.1005267652252045, 0.6984197043243866],
+                   [0.1005267652252045, 0.6984197043243866, 0.1005267652252045],
+                   [0.0568813795204234, 0.3143728734931922, 0.3143728734931922],
+                   [0.3143728734931922, 0.3143728734931922, 0.3143728734931922],
+                   [0.3143728734931922, 0.3143728734931922, 0.0568813795204234],
+                   [0.3143728734931922, 0.0568813795204234, 0.3143728734931922]])
+        w = arange(14, dtype=float64)
+        w[0:6] = 0.0190476190476190
+        w[6:10] = 0.0885898247429807
+        w[10:14] = 0.1328387466855907
+        w = w/6.0
+    elif degree == 5:
+        # Keast rule, 15 points, degree of precision 5
+        # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html
+        # (KEAST6)
+        x = array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000],
+                   [0.0000000000000000, 0.3333333333333333, 0.3333333333333333],
+                   [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
+                   [0.3333333333333333, 0.3333333333333333, 0.0000000000000000],
+                   [0.3333333333333333, 0.0000000000000000, 0.3333333333333333],
+                   [0.7272727272727273, 0.0909090909090909, 0.0909090909090909],
+                   [0.0909090909090909, 0.0909090909090909, 0.0909090909090909],
+                   [0.0909090909090909, 0.0909090909090909, 0.7272727272727273],
+                   [0.0909090909090909, 0.7272727272727273, 0.0909090909090909],
+                   [0.4334498464263357, 0.0665501535736643, 0.0665501535736643],
+                   [0.0665501535736643, 0.4334498464263357, 0.0665501535736643],
+                   [0.0665501535736643, 0.0665501535736643, 0.4334498464263357],
+                   [0.0665501535736643, 0.4334498464263357, 0.4334498464263357],
+                   [0.4334498464263357, 0.0665501535736643, 0.4334498464263357],
+                   [0.4334498464263357, 0.4334498464263357, 0.0665501535736643]])
+        w = arange(15, dtype=float64)
+        w[0] = 0.1817020685825351
+        w[1:5] = 0.0361607142857143
+        w[5:9] = 0.0698714945161738
+        w[9:15] = 0.0656948493683187
+        w = w/6.0
+    elif degree == 6:
+        # Keast rule, 24 points, degree of precision 6
+        # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html
+        # (KEAST7)
+        x = array([[0.3561913862225449, 0.2146028712591517, 0.2146028712591517],
+                   [0.2146028712591517, 0.2146028712591517, 0.2146028712591517],
+                   [0.2146028712591517, 0.2146028712591517, 0.3561913862225449],
+                   [0.2146028712591517, 0.3561913862225449, 0.2146028712591517],
+                   [0.8779781243961660, 0.0406739585346113, 0.0406739585346113],
+                   [0.0406739585346113, 0.0406739585346113, 0.0406739585346113],
+                   [0.0406739585346113, 0.0406739585346113, 0.8779781243961660],
+                   [0.0406739585346113, 0.8779781243961660, 0.0406739585346113],
+                   [0.0329863295731731, 0.3223378901422757, 0.3223378901422757],
+                   [0.3223378901422757, 0.3223378901422757, 0.3223378901422757],
+                   [0.3223378901422757, 0.3223378901422757, 0.0329863295731731],
+                   [0.3223378901422757, 0.0329863295731731, 0.3223378901422757],
+                   [0.2696723314583159, 0.0636610018750175, 0.0636610018750175],
+                   [0.0636610018750175, 0.2696723314583159, 0.0636610018750175],
+                   [0.0636610018750175, 0.0636610018750175, 0.2696723314583159],
+                   [0.6030056647916491, 0.0636610018750175, 0.0636610018750175],
+                   [0.0636610018750175, 0.6030056647916491, 0.0636610018750175],
+                   [0.0636610018750175, 0.0636610018750175, 0.6030056647916491],
+                   [0.0636610018750175, 0.2696723314583159, 0.6030056647916491],
+                   [0.2696723314583159, 0.6030056647916491, 0.0636610018750175],
+                   [0.6030056647916491, 0.0636610018750175, 0.2696723314583159],
+                   [0.0636610018750175, 0.6030056647916491, 0.2696723314583159],
+                   [0.2696723314583159, 0.0636610018750175, 0.6030056647916491],
+                   [0.6030056647916491, 0.2696723314583159, 0.0636610018750175]])
+        w = arange(24, dtype=float64)
+        w[0:4] = 0.0399227502581679
+        w[4:8] = 0.0100772110553207
+        w[8:12] = 0.0553571815436544
+        w[12:24] = 0.0482142857142857
+        w = w/6.0
+    else:
+        # Get canonical scheme
+        return _fiat_scheme(UFCTetrahedron(), degree)
+
+    # Return scheme
+    return QuadratureRule(UFCTetrahedron(), x, w)
diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py
index 6704b1c..aa700c4 100644
--- a/FIAT/raviart_thomas.py
+++ b/FIAT/raviart_thomas.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
+# Modified by Andrew T. T. McRae (Imperial College London)
 #
 # This file is part of FIAT.
 #
@@ -15,65 +16,69 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-from . import expansions, polynomial_set, quadrature, reference_element, dual_set, \
-    quadrature, finite_element, functional
+from __future__ import absolute_import, print_function, division
+
+from FIAT import (expansions, polynomial_set, quadrature, dual_set,
+                  finite_element, functional)
 import numpy
-from functools import reduce
+from itertools import chain
+
 
-def RTSpace( ref_el, deg ):
+def RTSpace(ref_el, deg):
     """Constructs a basis for the the Raviart-Thomas space
     (P_k)^d + P_k x"""
     sd = ref_el.get_spatial_dimension()
 
-    vec_Pkp1 = polynomial_set.ONPolynomialSet( ref_el, deg+1, (sd,) )
+    vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, deg + 1, (sd,))
 
-    dimPkp1 = expansions.polynomial_dimension( ref_el, deg+1 )
-    dimPk = expansions.polynomial_dimension( ref_el, deg )
-    dimPkm1 = expansions.polynomial_dimension( ref_el, deg-1 )
+    dimPkp1 = expansions.polynomial_dimension(ref_el, deg + 1)
+    dimPk = expansions.polynomial_dimension(ref_el, deg)
+    dimPkm1 = expansions.polynomial_dimension(ref_el, deg - 1)
 
-    vec_Pk_indices = reduce( lambda a, b: a+b, \
-                             [ list(range(i*dimPkp1, i*dimPkp1+dimPk)) \
-                               for i in range(sd) ] )
-    vec_Pk_from_Pkp1 = vec_Pkp1.take( vec_Pk_indices )
+    vec_Pk_indices = list(chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk)
+                                  for i in range(sd))))
+    vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)
 
-    Pkp1 = polynomial_set.ONPolynomialSet( ref_el, deg + 1 )
-    PkH = Pkp1.take( list(range(dimPkm1, dimPk)) )
+    Pkp1 = polynomial_set.ONPolynomialSet(ref_el, deg + 1)
+    PkH = Pkp1.take(list(range(dimPkm1, dimPk)))
 
-    Q = quadrature.make_quadrature( ref_el, 2 * deg + 2 )
+    Q = quadrature.make_quadrature(ref_el, 2 * deg + 2)
 
     # have to work on this through "tabulate" interface
     # first, tabulate PkH at quadrature points
-    Qpts = numpy.array( Q.get_points() )
-    Qwts = numpy.array( Q.get_weights() )
+    Qpts = numpy.array(Q.get_points())
+    Qwts = numpy.array(Q.get_weights())
 
-    zero_index = tuple( [ 0 for i in range(sd) ] )
+    zero_index = tuple([0 for i in range(sd)])
 
-    PkH_at_Qpts = PkH.tabulate( Qpts )[zero_index]
-    Pkp1_at_Qpts = Pkp1.tabulate( Qpts )[zero_index]
+    PkH_at_Qpts = PkH.tabulate(Qpts)[zero_index]
+    Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index]
 
-    PkHx_coeffs = numpy.zeros( (PkH.get_num_members(), \
-                                sd, \
-                                Pkp1.get_num_members()), "d" )
+    PkHx_coeffs = numpy.zeros((PkH.get_num_members(),
+                               sd,
+                               Pkp1.get_num_members()), "d")
 
-    for i in range( PkH.get_num_members() ):
-        for j in range( sd ):
-            fooij = PkH_at_Qpts[i,:] * Qpts[:, j] * Qwts
-            PkHx_coeffs[i, j,:] = numpy.dot( Pkp1_at_Qpts, fooij )
+    for i in range(PkH.get_num_members()):
+        for j in range(sd):
+            fooij = PkH_at_Qpts[i, :] * Qpts[:, j] * Qwts
+            PkHx_coeffs[i, j, :] = numpy.dot(Pkp1_at_Qpts, fooij)
 
-    PkHx = polynomial_set.PolynomialSet( ref_el, \
-                                         deg, \
-                                         deg + 1, \
-                                         vec_Pkp1.get_expansion_set(), \
-                                         PkHx_coeffs, \
-                                         vec_Pkp1.get_dmats() )
+    PkHx = polynomial_set.PolynomialSet(ref_el,
+                                        deg,
+                                        deg + 1,
+                                        vec_Pkp1.get_expansion_set(),
+                                        PkHx_coeffs,
+                                        vec_Pkp1.get_dmats())
 
-    return polynomial_set.polynomial_set_union_normalized( vec_Pk_from_Pkp1, PkHx )
+    return polynomial_set.polynomial_set_union_normalized(vec_Pk_from_Pkp1, PkHx)
 
-class RTDualSet( dual_set.DualSet ):
+
+class RTDualSet(dual_set.DualSet):
     """Dual basis for Raviart-Thomas elements consisting of point
     evaluation of normals on facets of codimension 1 and internal
     moments against polynomials"""
-    def __init__( self, ref_el, degree ):
+
+    def __init__(self, ref_el, degree):
         entity_ids = {}
         nodes = []
 
@@ -81,89 +86,68 @@ class RTDualSet( dual_set.DualSet ):
         t = ref_el.get_topology()
 
         # codimension 1 facets
-        for i in range( len( t[sd-1] ) ):
-            pts_cur = ref_el.make_points( sd - 1, i, sd + degree )
-            for j in range( len( pts_cur ) ):
+        for i in range(len(t[sd - 1])):
+            pts_cur = ref_el.make_points(sd - 1, i, sd + degree)
+            for j in range(len(pts_cur)):
                 pt_cur = pts_cur[j]
-                f = functional.PointScaledNormalEvaluation( ref_el, i, \
-                                                            pt_cur )
-                nodes.append( f )
+                f = functional.PointScaledNormalEvaluation(ref_el, i, pt_cur)
+                nodes.append(f)
 
         # internal nodes.  Let's just use points at a lattice
         if degree > 0:
             cpe = functional.ComponentPointEvaluation
-            pts = ref_el.make_points( sd, 0, degree + sd )
-            for d in range( sd ):
-                for i in range( len( pts ) ):
-                    l_cur = cpe( ref_el, d, (sd,), pts[i] )
-                    nodes.append( l_cur )
-
-#            Q = quadrature.make_quadrature( ref_el , 2 * ( degree + 1 ) )
-#            qpts = Q.get_points()
-#            Pkm1 = polynomial_set.ONPolynomialSet( ref_el , degree - 1 )
-#            zero_index = tuple( [ 0 for i in range( sd ) ]  )
-#            Pkm1_at_qpts = Pkm1.tabulate( qpts )[ zero_index ]
-
-#            for d in range( sd ):
-#                for i in range( Pkm1_at_qpts.shape[0] ):
-#                    phi_cur = Pkm1_at_qpts[i,:]
-#                    l_cur = functional.IntegralMoment( ref_el , Q , \
-#                                                       phi_cur , (d,) , (sd,) )
-#                    nodes.append( l_cur )
+            pts = ref_el.make_points(sd, 0, degree + sd)
+            for d in range(sd):
+                for i in range(len(pts)):
+                    l_cur = cpe(ref_el, d, (sd,), pts[i])
+                    nodes.append(l_cur)
+
+            # Q = quadrature.make_quadrature(ref_el, 2 * ( degree + 1 ))
+            # qpts = Q.get_points()
+            # Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 1)
+            # zero_index = tuple([0 for i in range(sd)])
+            # Pkm1_at_qpts = Pkm1.tabulate(qpts)[zero_index]
+
+            # for d in range(sd):
+            #     for i in range(Pkm1_at_qpts.shape[0]):
+            #         phi_cur = Pkm1_at_qpts[i, :]
+            #         l_cur = functional.IntegralMoment(ref_el, Q, phi_cur, (d,), (sd,))
+            #         nodes.append(l_cur)
 
         # sets vertices (and in 3d, edges) to have no nodes
-        for i in range( sd - 1 ):
+        for i in range(sd - 1):
             entity_ids[i] = {}
-            for j in range( len( t[i] ) ):
+            for j in range(len(t[i])):
                 entity_ids[i][j] = []
 
         cur = 0
 
         # set codimension 1 (edges 2d, faces 3d) dof
-        pts_facet_0 = ref_el.make_points( sd - 1, 0, sd + degree )
-        pts_per_facet = len( pts_facet_0 )
-        entity_ids[sd-1] = {}
-        for i in range( len( t[sd-1] ) ):
-            entity_ids[sd-1][i] = list(range( cur, cur + pts_per_facet))
+        pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree)
+        pts_per_facet = len(pts_facet_0)
+        entity_ids[sd - 1] = {}
+        for i in range(len(t[sd - 1])):
+            entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet))
             cur += pts_per_facet
 
         # internal nodes, if applicable
         entity_ids[sd] = {0: []}
         if degree > 0:
-            num_internal_nodes = expansions.polynomial_dimension( ref_el, \
-                                                                  degree - 1 )
-            entity_ids[sd][0] = list(range( cur, cur + num_internal_nodes * sd))
+            num_internal_nodes = expansions.polynomial_dimension(ref_el,
+                                                                 degree - 1)
+            entity_ids[sd][0] = list(range(cur, cur + num_internal_nodes * sd))
 
-        dual_set.DualSet.__init__( self, nodes, ref_el, entity_ids )
+        super(RTDualSet, self).__init__(nodes, ref_el, entity_ids)
 
 
-class RaviartThomas( finite_element.FiniteElement ):
+class RaviartThomas(finite_element.CiarletElement):
     """The Raviart-Thomas finite element"""
-    def __init__( self, ref_el, q ):
-
-        degree = q - 1
-        poly_set = RTSpace( ref_el, degree )
-        dual = RTDualSet( ref_el, degree )
-        finite_element.FiniteElement.__init__( self, poly_set, dual, degree,
-                                               mapping="contravariant piola")
-
-
-if __name__=="__main__":
-    T = reference_element.UFCTriangle()
-    sd = T.get_spatial_dimension()
 
-    for k in range(6):
-        RT = RaviartThomas( T, k )
-
-
-#    RTfs = RT.get_nodal_basis()
-
-#    pts = T.make_lattice( 1 )
-#    print pts
-
-#    zero_index = tuple( [ 0 for i in range(sd) ] )
-#
-#    RTvals = RTfs.tabulate( pts )[zero_index]
-
-#    print RTvals
+    def __init__(self, ref_el, q):
 
+        degree = q - 1
+        poly_set = RTSpace(ref_el, degree)
+        dual = RTDualSet(ref_el, degree)
+        formdegree = ref_el.get_spatial_dimension() - 1  # (n-1)-form
+        super(RaviartThomas, self).__init__(poly_set, dual, degree, formdegree,
+                                            mapping="contravariant piola")
diff --git a/FIAT/reference_element.py b/FIAT/reference_element.py
index 88e2b4f..72cd47c 100644
--- a/FIAT/reference_element.py
+++ b/FIAT/reference_element.py
@@ -14,7 +14,9 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
+#
+# Modified by David A. Ham (david.ham at imperial.ac.uk), 2014
+# Modified by Lizao Li (lzlarryli at gmail.com), 2016
 """
 Abstract class and particular implementations of finite element
 reference simplex geometry/topology.
@@ -27,14 +29,28 @@ and orderings of entities have a single point of entry.
 
 Currently implemented are UFC and Default Line, Triangle and Tetrahedron.
 """
+from __future__ import absolute_import, print_function, division
 
+from itertools import chain, product
+import operator
+from math import factorial
+
+from six import iteritems, itervalues
+from six import string_types
+from six.moves import reduce
 import numpy
+from numpy import ravel_multi_index, transpose
+
 
+POINT = 0
 LINE = 1
 TRIANGLE = 2
 TETRAHEDRON = 3
+QUADRILATERAL = 11
+TENSORPRODUCT = 99
 
-def linalg_subspace_intersection( A, B ):
+
+def linalg_subspace_intersection(A, B):
     """Computes the intersection of the subspaces spanned by the
     columns of 2-dimensional arrays A,B using the algorithm found in
     Golub and van Loan (3rd ed) p. 604.  A should be in
@@ -46,110 +62,183 @@ def linalg_subspace_intersection( A, B ):
     if A.shape[0] != B.shape[0]:
         raise Exception("Dimension error")
 
-    #A,B are matrices of column vectors
+    # A,B are matrices of column vectors
     # compute the intersection of span(A) and span(B)
 
     # Compute the principal vectors/angles between the subspaces, G&vL
     # p.604
-    (qa, ra) = numpy.linalg.qr( A )
-    (qb, rb) = numpy.linalg.qr( B )
+    (qa, ra) = numpy.linalg.qr(A)
+    (qb, rb) = numpy.linalg.qr(B)
 
-    C = numpy.dot( numpy.transpose( qa ), qb )
+    C = numpy.dot(numpy.transpose(qa), qb)
 
-    (y, c, zt) = numpy.linalg.svd( C )
+    (y, c, zt) = numpy.linalg.svd(C)
 
-    U = numpy.dot( qa, y )
-    V = numpy.dot( qb, numpy.transpose( zt ) )
+    U = numpy.dot(qa, y)
 
-    rank_c = len( [ s for s in c if numpy.abs( 1.0 - s ) < 1.e-10 ] )
+    rank_c = len([s for s in c if numpy.abs(1.0 - s) < 1.e-10])
 
     return U[:, :rank_c]
 
-def lattice_iter( start, finish, depth ):
+
+def lattice_iter(start, finish, depth):
     """Generator iterating over the depth-dimensional lattice of
     integers between start and (finish-1).  This works on simplices in
     1d, 2d, 3d, and beyond"""
     if depth == 0:
         return
     elif depth == 1:
-        for ii in range( start, finish ):
+        for ii in range(start, finish):
             yield [ii]
     else:
-        for ii in range( start, finish ):
-            for jj in lattice_iter( start, finish-ii, depth - 1 ):
+        for ii in range(start, finish):
+            for jj in lattice_iter(start, finish - ii, depth - 1):
                 yield [ii] + jj
 
-class ReferenceElement:
-    """Abstract class for a reference element simplex.  Provides
-    accessors for geometry (vertex coordinates) as well as topology
-    (orderings of vertices that make up edges, facecs, etc."""
-    def __init__( self, shape, vertices, topology ):
-        """The constructor takes a shape code,
-        the physical vertices expressed as a list of tuples
-        of numbers, and the topology of a simplex.
-        The topology is stored as a dictionary of dictionaries
-        t[i][j] where i is the spatial dimension and j is the
-        index of the facet of that dimension.  The result is
-        a list of the vertices comprising the facet.
-        """
+
+class Cell(object):
+    """Abstract class for a reference cell.  Provides accessors for
+    geometry (vertex coordinates) as well as topology (orderings of
+    vertices that make up edges, facecs, etc."""
+
+    def __init__(self, shape, vertices, topology):
+        """The constructor takes a shape code, the physical vertices expressed
+        as a list of tuples of numbers, and the topology of a cell.
+
+        The topology is stored as a dictionary of dictionaries t[i][j]
+        where i is the dimension and j is the index of the facet of
+        that dimension.  The result is a list of the vertices
+        comprising the facet."""
         self.shape = shape
         self.vertices = vertices
         self.topology = topology
-    def get_shape( self ):
+
+        # Given the topology, work out for each entity in the cell,
+        # which other entities it contains.
+        self.sub_entities = {}
+        for dim, entities in iteritems(topology):
+            self.sub_entities[dim] = {}
+
+            for e, v in iteritems(entities):
+                vertices = frozenset(v)
+                sub_entities = []
+
+                for dim_, entities_ in iteritems(topology):
+                    for e_, vertices_ in iteritems(entities_):
+                        if vertices.issuperset(vertices_):
+                            sub_entities.append((dim_, e_))
+
+                self.sub_entities[dim][e] = sorted(sub_entities)
+
+    def _key(self):
+        """Hashable object key data (excluding type)."""
+        # Default: only type matters
+        return None
+
+    def __eq__(self, other):
+        return type(self) == type(other) and self._key() == other._key()
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __hash__(self):
+        return hash((type(self), self._key()))
+
+    def get_shape(self):
         """Returns the code for the element's shape."""
         return self.shape
-    def get_vertices( self ):
-        """Returns an iteratble of the element's vertices, each stored
-        as a tuple."""
+
+    def get_vertices(self):
+        """Returns an iterable of the element's vertices, each stored as a
+        tuple."""
         return self.vertices
-    def get_spatial_dimension( self ):
+
+    def get_spatial_dimension(self):
         """Returns the spatial dimension in which the element lives."""
-        return len( self.vertices[ 0 ] )
-    def get_topology( self ):
+        return len(self.vertices[0])
+
+    def get_topology(self):
         """Returns a dictionary encoding the topology of the element.
-        The dictionary's keys are the spatial dimensions (0,1,...)
-        and each value is a dictionary mapping
-        """
+
+        The dictionary's keys are the spatial dimensions (0, 1, ...)
+        and each value is a dictionary mapping."""
         return self.topology
-    def get_vertices_of_subcomplex( self, t ):
-        """Returns the tuple of vertex coordinates associated with the
-        labels contained in the iterable t."""
-        return tuple( [ self.vertices[ ti ] for ti in t ] )
-    def compute_normal( self, facet_i ):
+
+    def get_vertices_of_subcomplex(self, t):
+        """Returns the tuple of vertex coordinates associated with the labels
+        contained in the iterable t."""
+        return tuple([self.vertices[ti] for ti in t])
+
+    def get_dimension(self):
+        """Returns the subelement dimension of the cell.  For tensor
+        product cells, this a tuple of dimensions for each cell in the
+        product.  For all other cells, this is the same as the spatial
+        dimension."""
+        raise NotImplementedError("Should be implemented in a subclass.")
+
+    def construct_subelement(self, dimension):
+        """Constructs the reference element of a cell subentity
+        specified by subelement dimension.
+
+        :arg dimension: `tuple` for tensor product cells, `int` otherwise
+        """
+        raise NotImplementedError("Should be implemented in a subclass.")
+
+    def get_entity_transform(self, dim, entity_i):
+        """Returns a mapping of point coordinates from the
+        `entity_i`-th subentity of dimension `dim` to the cell.
+
+        :arg dim: `tuple` for tensor product cells, `int` otherwise
+        :arg entity_i: entity number (integer)
+        """
+        raise NotImplementedError("Should be implemented in a subclass.")
+
+
+class Simplex(Cell):
+    """Abstract class for a reference simplex."""
+
+    def compute_normal(self, facet_i):
         """Returns the unit normal vector to facet i of codimension 1."""
+        # Interval case
+        if self.get_shape() == LINE:
+            verts = numpy.asarray(self.vertices)
+            v_i, = self.get_topology()[0][facet_i]
+            n = verts[v_i] - verts[[1, 0][v_i]]
+            return n / numpy.linalg.norm(n)
+
         # first, let's compute the span of the simplex
         # This is trivial if we have a d-simplex in R^d.
         # Not so otherwise.
-        vert_vecs =  [ numpy.array( v ) \
-                       for v in self.vertices ]
-        vert_vecs_foo = numpy.array( [ vert_vecs[i] - vert_vecs[0] \
-                                       for i in range(1, len(vert_vecs) ) ] )
+        vert_vecs = [numpy.array(v)
+                     for v in self.vertices]
+        vert_vecs_foo = numpy.array([vert_vecs[i] - vert_vecs[0]
+                                     for i in range(1, len(vert_vecs))])
 
-        (u, s, vt) = numpy.linalg.svd( vert_vecs_foo )
-        rank = len( [ si for si in s if si > 1.e-10 ] )
+        (u, s, vt) = numpy.linalg.svd(vert_vecs_foo)
+        rank = len([si for si in s if si > 1.e-10])
 
         # this is the set of vectors that span the simplex
         spanu = u[:, :rank]
 
-        t = self.get_topology( )
+        t = self.get_topology()
         sd = self.get_spatial_dimension()
         vert_coords_of_facet = \
-            self.get_vertices_of_subcomplex( t[sd-1][facet_i] )
+            self.get_vertices_of_subcomplex(t[sd-1][facet_i])
 
         # now I find everything normal to the facet.
-        vcf = [ numpy.array( foo ) \
-                for foo in vert_coords_of_facet ]
-        facet_span = numpy.array( [ vcf[i] - vcf[0] \
-                                    for i in range(1, len(vcf) ) ] )
-        (uf, sf, vft) = numpy.linalg.svd( facet_span )
+        vcf = [numpy.array(foo)
+               for foo in vert_coords_of_facet]
+        facet_span = numpy.array([vcf[i] - vcf[0]
+                                  for i in range(1, len(vcf))])
+        (uf, sf, vft) = numpy.linalg.svd(facet_span)
 
         # now get the null space from vft
-        rankfacet = len( [ si for si in sf if si > 1.e-10 ] )
-        facet_normal_space = numpy.transpose( vft[rankfacet:,:] )
+        rankfacet = len([si for si in sf if si > 1.e-10])
+        facet_normal_space = numpy.transpose(vft[rankfacet:, :])
 
         # now, I have to compute the intersection of
         # facet_span with facet_normal_space
-        foo = linalg_subspace_intersection( facet_normal_space, spanu )
+        foo = linalg_subspace_intersection(facet_normal_space, spanu)
 
         num_cols = foo.shape[1]
 
@@ -161,65 +250,78 @@ class ReferenceElement:
         nfoo = foo[:, 0]
 
         # what is the vertex not in the facet?
-        verts_set = set( t[sd][0] )
-        verts_facet = set( t[sd-1][facet_i] )
-        verts_diff = verts_set.difference( verts_facet )
-        if len( verts_diff ) != 1:
+        verts_set = set(t[sd][0])
+        verts_facet = set(t[sd - 1][facet_i])
+        verts_diff = verts_set.difference(verts_facet)
+        if len(verts_diff) != 1:
             raise Exception("barf in normal computation: getting sign")
         vert_off = verts_diff.pop()
         vert_on = verts_facet.pop()
 
         # get a vector from the off vertex to the facet
-        v_to_facet = numpy.array( self.vertices[vert_on] ) \
-            - numpy.array( self.vertices[ vert_off ] )
+        v_to_facet = numpy.array(self.vertices[vert_on]) \
+            - numpy.array(self.vertices[vert_off])
 
-        if numpy.dot( v_to_facet, nfoo ) > 0.0:
+        if numpy.dot(v_to_facet, nfoo) > 0.0:
             return nfoo
         else:
             return -nfoo
 
-    def compute_tangents( self, dim, i ):
+    def compute_tangents(self, dim, i):
         """computes tangents in any dimension based on differences
         between vertices and the first vertex of the i:th facet
         of dimension dim.  Returns a (possibly empty) list.
         These tangents are *NOT* normalized to have unit length."""
         t = self.get_topology()
-        vs = list(map( numpy.array, \
-                  self.get_vertices_of_subcomplex( t[dim][i] ) ))
-        ts = [ v - vs[0] for v in vs[1:] ]
+        vs = list(map(numpy.array, self.get_vertices_of_subcomplex(t[dim][i])))
+        ts = [v - vs[0] for v in vs[1:]]
         return ts
 
-    def compute_normalized_tangents( self, dim, i ):
+    def compute_normalized_tangents(self, dim, i):
         """computes tangents in any dimension based on differences
         between vertices and the first vertex of the i:th facet
         of dimension dim.  Returns a (possibly empty) list.
         These tangents are normalized to have unit length."""
-        ts = self.compute_tangents( dim, i )
-        return [ t / numpy.linalg.norm( t ) for t in ts ]
+        ts = self.compute_tangents(dim, i)
+        return [t / numpy.linalg.norm(t) for t in ts]
 
-    def compute_edge_tangent( self, edge_i ):
+    def compute_edge_tangent(self, edge_i):
         """Computes the nonnormalized tangent to a 1-dimensional facet.
         returns a single vector."""
         t = self.get_topology()
-        (v0, v1) = self.get_vertices_of_subcomplex( t[1][edge_i] )
-        return numpy.array( v1 ) - numpy.array( v0 )
+        (v0, v1) = self.get_vertices_of_subcomplex(t[1][edge_i])
+        return numpy.array(v1) - numpy.array(v0)
 
-    def compute_normalized_edge_tangent( self, edge_i ):
+    def compute_normalized_edge_tangent(self, edge_i):
         """Computes the unit tangent vector to a 1-dimensional facet"""
-        v = self.compute_edge_tangent( edge_i )
-        return v / numpy.linalg.norm( v )
+        v = self.compute_edge_tangent(edge_i)
+        return v / numpy.linalg.norm(v)
 
-    def compute_face_tangents( self, face_i ):
+    def compute_face_tangents(self, face_i):
         """Computes the two tangents to a face.  Only implemented
         for a tetrahedron."""
         if self.get_spatial_dimension() != 3:
             raise Exception("can't get face tangents yet")
         t = self.get_topology()
-        (v0, v1, v2) = list(map( numpy.array, \
-                          self.get_vertices_of_subcomplex( t[2][face_i] ) ))
-        return (v1-v0, v2-v0)
-
-    def make_lattice( self , n , interior = 0):
+        (v0, v1, v2) = list(map(numpy.array,
+                                self.get_vertices_of_subcomplex(t[2][face_i])))
+        return (v1 - v0, v2 - v0)
+
+    def compute_face_edge_tangents(self, dim, entity_id):
+        """Computes all the edge tangents of any k-face with k>=1.
+        The result is a array of binom(dim+1,2) vectors.
+        This agrees with `compute_edge_tangent` when dim=1.
+        """
+        vert_ids = self.get_topology()[dim][entity_id]
+        vert_coords = [numpy.array(x)
+                       for x in self.get_vertices_of_subcomplex(vert_ids)]
+        edge_ts = []
+        for source in range(dim):
+            for dest in range(source + 1, dim + 1):
+                edge_ts.append(vert_coords[dest] - vert_coords[source])
+        return edge_ts
+
+    def make_lattice(self, n, interior=0):
         """Constructs a lattice of points on the simplex.  For
         example, the 1:st order lattice will be just the vertices.
         The optional argument interior specifies how many points from
@@ -228,246 +330,551 @@ class ReferenceElement:
         midpoint, but with interior = 1, it will only return the
         midpoint."""
         verts = self.get_vertices()
-        nverts = len( verts )
-        vs = [ numpy.array( v ) for v in verts ]
-        hs = [ (vs[ i ] - vs[ 0 ]) / n for i in range(1, nverts) ]
+        nverts = len(verts)
+        vs = [numpy.array(v) for v in verts]
+        hs = [(vs[i] - vs[0]) / n for i in range(1, nverts)]
 
         result = []
 
-        m = len( hs )
+        m = len(hs)
 
-        for indices in lattice_iter( interior, n + 1 - interior, m ):
+        for indices in lattice_iter(interior, n + 1 - interior, m):
             res_cur = vs[0].copy()
             for i in range(len(indices)):
-                res_cur += indices[i] * hs[m-i-1]
-            result.append( tuple( res_cur ) )
+                res_cur += indices[i] * hs[m - i - 1]
+            result.append(tuple(res_cur))
 
         return result
 
-    def make_points( self, dim, entity_id, order ):
+    def make_points(self, dim, entity_id, order):
         """Constructs a lattice of points on the entity_id:th
         facet of dimension dim.  Order indicates how many points to
         include in each direction."""
         if dim == 0:
-            return ( self.get_vertices()[entity_id], )
+            return (self.get_vertices()[entity_id], )
         elif dim > self.get_spatial_dimension():
             raise Exception("illegal dimension")
         elif dim == self.get_spatial_dimension():
-            return self.make_lattice( order, 1 )
+            return self.make_lattice(order, 1)
         else:
-            base_el = default_simplex( dim )
+            base_el = default_simplex(dim)
             base_verts = base_el.get_vertices()
             facet_verts = \
-                        self.get_vertices_of_subcomplex( \
-                            self.get_topology()[dim][entity_id] )
+                self.get_vertices_of_subcomplex(
+                    self.get_topology()[dim][entity_id])
 
-            (A, b) = make_affine_mapping( base_verts, facet_verts )
+            (A, b) = make_affine_mapping(base_verts, facet_verts)
 
-            f = lambda x: (numpy.dot( A, x ) + b)
-            base_pts = base_el.make_lattice( order, 1 )
-            image_pts = tuple( [ tuple( f( x ) ) for x in base_pts ] )
+            f = lambda x: (numpy.dot(A, x) + b)
+            base_pts = base_el.make_lattice(order, 1)
+            image_pts = tuple([tuple(f(x)) for x in base_pts])
 
             return image_pts
 
-    def volume( self ):
-        """Computes the volumne of the simplex in the appropriate
+    def volume(self):
+        """Computes the volume of the simplex in the appropriate
         dimensional measure."""
-        return volume( self.get_vertices() )
+        return volume(self.get_vertices())
 
-    def volume_of_subcomplex( self, dim, facet_no ):
+    def volume_of_subcomplex(self, dim, facet_no):
         vids = self.topology[dim][facet_no]
-        return volume( self.get_vertices_of_subcomplex( vids ) )
+        return volume(self.get_vertices_of_subcomplex(vids))
 
-    def compute_scaled_normal( self, facet_i ):
+    def compute_scaled_normal(self, facet_i):
         """Returns the unit normal to facet_i of scaled by the
         volume of that facet."""
+        dim = self.get_spatial_dimension()
+        v = self.volume_of_subcomplex(dim - 1, facet_i)
+        return self.compute_normal(facet_i) * v
+
+    def compute_reference_normal(self, facet_dim, facet_i):
+        """Returns the unit normal in infinity norm to facet_i."""
+        assert facet_dim == self.get_spatial_dimension() - 1
+        n = Simplex.compute_normal(self, facet_i)  # skip UFC overrides
+        return n / numpy.linalg.norm(n, numpy.inf)
+
+    def get_facet_transform(self, facet_i):
+        """Return a function f such that for a point with facet coordinates
+        x_f on facet_i, x_c = f(x_f) is the corresponding cell coordinates.
+        """
         t = self.get_topology()
-        sd = self.get_spatial_dimension()
-        facet_verts_ids = t[sd-1][facet_i]
-        facet_verts_coords = self.get_vertices_of_subcomplex( facet_verts_ids )
 
-        v = volume( facet_verts_coords )
+        try:
+            f_el = self.get_facet_element()
+        except NotImplementedError:
+            # Special case for 1D elements.
+            x_c = self.get_vertices_of_subcomplex(t[0][facet_i])[0]
+
+            return lambda x: x_c
 
-        return self.compute_normal( facet_i ) * v
+        sd_c = self.get_spatial_dimension()
+        sd_f = f_el.get_spatial_dimension()
+
+        # Facet vertices in facet space.
+        v_f = numpy.array(f_el.get_vertices())
+
+        A = numpy.zeros([sd_f, sd_f])
+
+        for i in range(A.shape[0]):
+            A[i, :] = (v_f[i + 1] - v_f[0])
+            A[i, :] /= A[i, :].dot(A[i, :])
+
+        # Facet vertices in cell space.
+        v_c = numpy.array(self.get_vertices_of_subcomplex(t[sd_c - 1][facet_i]))
+
+        B = numpy.zeros([sd_c, sd_f])
+
+        for j in range(B.shape[1]):
+            B[:, j] = (v_c[j + 1] - v_c[0])
+
+        C = B.dot(A)
+
+        offset = v_c[0] - C.dot(v_f[0])
+
+        return lambda x: offset + C.dot(x)
+
+    def get_dimension(self):
+        """Returns the subelement dimension of the cell.  Same as the
+        spatial dimension."""
+        return self.get_spatial_dimension()
+
+    def get_entity_transform(self, dim, entity_i):
+        """Returns a mapping of point coordinates from the
+        `entity_i`-th subentity of dimension `dim` to the cell.
+
+        :arg dim: subentity dimension (integer)
+        :arg entity_i: entity number (integer)
+        """
+        space_dim = self.get_spatial_dimension()
+        if dim == space_dim:  # cell points
+            assert entity_i == 0
+            return lambda point: point
+        elif dim == space_dim - 1:  # facet points
+            return self.get_facet_transform(entity_i)
+        else:
+            raise NotImplementedError("Co-dimension >1 not implemented.")
 
-class DefaultLine( ReferenceElement ):
+
+# Backwards compatible name
+ReferenceElement = Simplex
+
+
+class UFCSimplex(Simplex):
+
+    def get_facet_element(self):
+        dimension = self.get_spatial_dimension()
+        return self.construct_subelement(dimension - 1)
+
+    def construct_subelement(self, dimension):
+        """Constructs the reference element of a cell subentity
+        specified by subelement dimension.
+
+        :arg dimension: subentity dimension (integer)
+        """
+        return ufc_simplex(dimension)
+
+    def contains_point(self, point, epsilon=0):
+        """Checks if reference cell contains given point
+        (with numerical tolerance)."""
+        result = (sum(point) - epsilon <= 1)
+        for c in point:
+            result &= (c + epsilon >= 0)
+        return result
+
+
+class Point(Simplex):
+    """This is the reference point."""
+
+    def __init__(self):
+        verts = ((),)
+        topology = {0: {0: (0,)}}
+        super(Point, self).__init__(POINT, verts, topology)
+
+
+class DefaultLine(Simplex):
     """This is the reference line with vertices (-1.0,) and (1.0,)."""
-    def __init__( self ):
-        verts = ( (-1.0,), (1.0,) )
-        edges = { 0 : ( 0, 1 ) }
-        topology = { 0 : { 0 : (0,) , 1: (1,) } , \
-                     1 : edges }
-        ReferenceElement.__init__( self, LINE, verts, topology )
-
-class UFCInterval( ReferenceElement ):
+
+    def __init__(self):
+        verts = ((-1.0,), (1.0,))
+        edges = {0: (0, 1)}
+        topology = {0: {0: (0,), 1: (1,)},
+                    1: edges}
+        super(DefaultLine, self).__init__(LINE, verts, topology)
+
+    def get_facet_element(self):
+        raise NotImplementedError()
+
+
+class UFCInterval(UFCSimplex):
     """This is the reference interval with vertices (0.0,) and (1.0,)."""
-    def __init__( self ):
-        verts = ( (0.0,), (1.0,) )
-        edges = { 0 : ( 0, 1 ) }
-        topology = { 0 : { 0 : (0,) , 1 : (1,) } , \
-                     1 : edges }
-        ReferenceElement.__init__( self, LINE, verts, topology )
-
-class DefaultTriangle( ReferenceElement ):
+
+    def __init__(self):
+        verts = ((0.0,), (1.0,))
+        edges = {0: (0, 1)}
+        topology = {0: {0: (0,), 1: (1,)},
+                    1: edges}
+        super(UFCInterval, self).__init__(LINE, verts, topology)
+
+
+class DefaultTriangle(Simplex):
     """This is the reference triangle with vertices (-1.0,-1.0),
     (1.0,-1.0), and (-1.0,1.0)."""
-    def __init__( self ):
+
+    def __init__(self):
         verts = ((-1.0, -1.0), (1.0, -1.0), (-1.0, 1.0))
-        edges = { 0 : ( 1, 2 ) , \
-                  1 : ( 2, 0 ) , \
-                  2 : ( 0, 1 ) }
-        faces = { 0 : ( 0, 1, 2 ) }
-        topology = { 0 : { 0 : (0,) , 1 : (1,) , 2 : (2,) } , \
-                     1 : edges , 2 : faces }
-        ReferenceElement.__init__( self, TRIANGLE, verts, topology )
-
-class UFCTriangle( ReferenceElement ):
+        edges = {0: (1, 2),
+                 1: (2, 0),
+                 2: (0, 1)}
+        faces = {0: (0, 1, 2)}
+        topology = {0: {0: (0,), 1: (1,), 2: (2,)},
+                    1: edges, 2: faces}
+        super(DefaultTriangle, self).__init__(TRIANGLE, verts, topology)
+
+    def get_facet_element(self):
+        return DefaultLine()
+
+
+class UFCTriangle(UFCSimplex):
     """This is the reference triangle with vertices (0.0,0.0),
     (1.0,0.0), and (0.0,1.0)."""
-    def __init__( self ):
+
+    def __init__(self):
         verts = ((0.0, 0.0), (1.0, 0.0), (0.0, 1.0))
-        edges = { 0 : ( 1, 2 ) , 1 : ( 0, 2 ) , 2 : ( 0, 1 ) }
-        faces = { 0 : ( 0, 1, 2 ) }
-        topology = { 0 : { 0 : (0,) , 1 : (1,) , 2 : (2,) } , \
-                     1 : edges , 2 : faces }
-        ReferenceElement.__init__( self, TRIANGLE, verts, topology )
+        edges = {0: (1, 2), 1: (0, 2), 2: (0, 1)}
+        faces = {0: (0, 1, 2)}
+        topology = {0: {0: (0,), 1: (1,), 2: (2,)},
+                    1: edges, 2: faces}
+        super(UFCTriangle, self).__init__(TRIANGLE, verts, topology)
 
     def compute_normal(self, i):
         "UFC consistent normal"
         t = self.compute_tangents(1, i)[0]
         n = numpy.array((t[1], -t[0]))
-        return n/numpy.linalg.norm(n)
+        return n / numpy.linalg.norm(n)
 
 
-class IntrepidTriangle( ReferenceElement ):
+class IntrepidTriangle(Simplex):
     """This is the Intrepid triangle with vertices (0,0),(1,0),(0,1)"""
-    def __init__( self ):
+
+    def __init__(self):
         verts = ((0.0, 0.0), (1.0, 0.0), (0.0, 1.0))
-        edges = { 0 : ( 0, 1 ) , \
-                  1 : ( 1, 2 ) , \
-                  2 : ( 2, 0 ) }
-        faces = { 0 : ( 0, 1, 2 ) }
-        topology = { 0 : { 0 : (0,) , 1 : (1,) , 2 : (2,) } , \
-                     1 : edges , 2 : faces }
-        ReferenceElement.__init__( self, TRIANGLE, verts, topology )
+        edges = {0: (0, 1),
+                 1: (1, 2),
+                 2: (2, 0)}
+        faces = {0: (0, 1, 2)}
+        topology = {0: {0: (0,), 1: (1,), 2: (2,)},
+                    1: edges, 2: faces}
+        super(IntrepidTriangle, self).__init__(TRIANGLE, verts, topology)
+
+    def get_facet_element(self):
+        # I think the UFC interval is equivalent to what the
+        # IntrepidInterval would be.
+        return UFCInterval()
 
 
-class DefaultTetrahedron( ReferenceElement ):
+class DefaultTetrahedron(Simplex):
     """This is the reference tetrahedron with vertices (-1,-1,-1),
     (1,-1,-1),(-1,1,-1), and (-1,-1,1)."""
-    def __init__( self ):
-        verts = ((-1.0, -1.0, -1.0), (1.0, -1.0, -1.0),\
+
+    def __init__(self):
+        verts = ((-1.0, -1.0, -1.0), (1.0, -1.0, -1.0),
                  (-1.0, 1.0, -1.0), (-1.0, -1.0, 1.0))
-        vs = { 0 : ( 0, ) , \
-               1 : ( 1, ) , \
-               2 : ( 2, ) , \
-               3 : ( 3, ) }
-        edges = { 0: ( 1, 2 ) , \
-                  1: ( 2, 0 ) , \
-                  2: ( 0, 1 ) , \
-                  3: ( 0, 3 ) , \
-                  4: ( 1, 3 ) , \
-                  5: ( 2, 3 ) }
-        faces = { 0 : ( 1, 3, 2 ) , \
-                  1 : ( 2, 3, 0 ) , \
-                  2 : ( 3, 1, 0 ) , \
-                  3 : ( 0, 1, 2 ) }
-        tets = { 0 : ( 0, 1, 2, 3 ) }
-        topology = { 0: vs , 1 : edges , 2 : faces , 3 : tets }
-        ReferenceElement.__init__( self, TETRAHEDRON, verts, topology )
-
-class IntrepidTetrahedron( ReferenceElement ):
+        vs = {0: (0, ),
+              1: (1, ),
+              2: (2, ),
+              3: (3, )}
+        edges = {0: (1, 2),
+                 1: (2, 0),
+                 2: (0, 1),
+                 3: (0, 3),
+                 4: (1, 3),
+                 5: (2, 3)}
+        faces = {0: (1, 3, 2),
+                 1: (2, 3, 0),
+                 2: (3, 1, 0),
+                 3: (0, 1, 2)}
+        tets = {0: (0, 1, 2, 3)}
+        topology = {0: vs, 1: edges, 2: faces, 3: tets}
+        super(DefaultTetrahedron, self).__init__(TETRAHEDRON, verts, topology)
+
+    def get_facet_element(self):
+        return DefaultTriangle()
+
+
+class IntrepidTetrahedron(Simplex):
     """This is the reference tetrahedron with vertices (0,0,0),
     (1,0,0),(0,1,0), and (0,0,1) used in the Intrepid project."""
-    def __init__( self ):
+
+    def __init__(self):
         verts = ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0))
-        vs = { 0 : ( 0, ) , \
-               1 : ( 1, ) , \
-               2 : ( 2, ) , \
-               3 : ( 3, ) }
-        edges = { 0 : (0, 1) , \
-                  1 : (1, 2) , \
-                  2 : (2, 0) , \
-                  3 : (0, 3) , \
-                  4 : (1, 3) , \
-                  5 : (2, 3) }
-        faces = { 0 : (0, 1, 3) , \
-                  1 : (1, 2, 3) , \
-                  2 : (0, 3, 2) , \
-                  3 : (0, 2, 1) }
-        tets = { 0 : ( 0, 1, 2, 3 ) }
-        topology = { 0: vs , 1 : edges , 2 : faces , 3 : tets }
-        ReferenceElement.__init__( self, TETRAHEDRON, verts, topology )
-
-
-class UFCTetrahedron( ReferenceElement ):
+        vs = {0: (0, ),
+              1: (1, ),
+              2: (2, ),
+              3: (3, )}
+        edges = {0: (0, 1),
+                 1: (1, 2),
+                 2: (2, 0),
+                 3: (0, 3),
+                 4: (1, 3),
+                 5: (2, 3)}
+        faces = {0: (0, 1, 3),
+                 1: (1, 2, 3),
+                 2: (0, 3, 2),
+                 3: (0, 2, 1)}
+        tets = {0: (0, 1, 2, 3)}
+        topology = {0: vs, 1: edges, 2: faces, 3: tets}
+        super(IntrepidTetrahedron, self).__init__(TETRAHEDRON, verts, topology)
+
+    def get_facet_element(self):
+        return IntrepidTriangle()
+
+
+class UFCTetrahedron(UFCSimplex):
     """This is the reference tetrahedron with vertices (0,0,0),
     (1,0,0),(0,1,0), and (0,0,1)."""
-    def __init__( self ):
+
+    def __init__(self):
         verts = ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0))
-        vs = { 0 : ( 0, ) , \
-               1 : ( 1, ) , \
-               2 : ( 2, ) , \
-               3 : ( 3, ) }
-        edges = { 0 : ( 2, 3 ) , \
-                  1 : ( 1, 3 ) , \
-                  2 : ( 1, 2 ) , \
-                  3 : ( 0, 3 ) , \
-                  4 : ( 0, 2 ) , \
-                  5 : ( 0, 1 ) }
-        faces = { 0 : ( 1, 2, 3 ) , \
-                  1 : ( 0, 2, 3 ) , \
-                  2 : ( 0, 1, 3 ) , \
-                  3 : ( 0, 1, 2 ) }
-        tets = { 0 : ( 0, 1, 2, 3 ) }
-        topology = { 0: vs , 1 : edges , 2 : faces , 3 : tets }
-        ReferenceElement.__init__( self, TETRAHEDRON, verts, topology )
+        vs = {0: (0, ),
+              1: (1, ),
+              2: (2, ),
+              3: (3, )}
+        edges = {0: (2, 3),
+                 1: (1, 3),
+                 2: (1, 2),
+                 3: (0, 3),
+                 4: (0, 2),
+                 5: (0, 1)}
+        faces = {0: (1, 2, 3),
+                 1: (0, 2, 3),
+                 2: (0, 1, 3),
+                 3: (0, 1, 2)}
+        tets = {0: (0, 1, 2, 3)}
+        topology = {0: vs, 1: edges, 2: faces, 3: tets}
+        super(UFCTetrahedron, self).__init__(TETRAHEDRON, verts, topology)
 
     def compute_normal(self, i):
         "UFC consistent normals."
         t = self.compute_tangents(2, i)
         n = numpy.cross(t[0], t[1])
-        return -2.0*n/numpy.linalg.norm(n)
+        return -2.0 * n / numpy.linalg.norm(n)
+
+
+class TensorProductCell(Cell):
+    """A cell that is the product of FIAT cells."""
+
+    def __init__(self, *cells):
+        # Vertices
+        vertices = tuple(tuple(chain(*coords))
+                         for coords in product(*[cell.get_vertices()
+                                                 for cell in cells]))
+
+        # Topology
+        shape = tuple(len(c.get_vertices()) for c in cells)
+        topology = {}
+        for dim in product(*[cell.get_topology().keys()
+                             for cell in cells]):
+            topology[dim] = {}
+            topds = [cell.get_topology()[d]
+                     for cell, d in zip(cells, dim)]
+            for tuple_ei in product(*[sorted(topd)for topd in topds]):
+                tuple_vs = list(product(*[topd[ei]
+                                          for topd, ei in zip(topds, tuple_ei)]))
+                vs = tuple(ravel_multi_index(transpose(tuple_vs), shape))
+                topology[dim][tuple_ei] = vs
+            # flatten entity numbers
+            topology[dim] = dict(enumerate(topology[dim][key]
+                                           for key in sorted(topology[dim])))
+
+        super(TensorProductCell, self).__init__(TENSORPRODUCT, vertices, topology)
+        self.cells = tuple(cells)
+
+    def _key(self):
+        return self.cells
+
+    @staticmethod
+    def _split_slices(lengths):
+        n = len(lengths)
+        delimiter = [0] * (n + 1)
+        for i in range(n):
+            delimiter[i + 1] = delimiter[i] + lengths[i]
+        return [slice(delimiter[i], delimiter[i+1])
+                for i in range(n)]
+
+    def get_dimension(self):
+        """Returns the subelement dimension of the cell, a tuple of
+        dimensions for each cell in the product."""
+        return tuple(c.get_dimension() for c in self.cells)
+
+    def construct_subelement(self, dimension):
+        """Constructs the reference element of a cell subentity
+        specified by subelement dimension.
+
+        :arg dimension: dimension in each "direction" (tuple)
+        """
+        return TensorProductCell(*[c.construct_subelement(d)
+                                   for c, d in zip(self.cells, dimension)])
+
+    def get_entity_transform(self, dim, entity_i):
+        """Returns a mapping of point coordinates from the
+        `entity_i`-th subentity of dimension `dim` to the cell.
 
+        :arg dim: subelement dimension (tuple)
+        :arg entity_i: entity number (integer)
+        """
+        # unravel entity_i
+        shape = tuple(len(c.get_topology()[d])
+                      for c, d in zip(self.cells, dim))
+        alpha = numpy.unravel_index(entity_i, shape)
+
+        # entity transform on each subcell
+        sct = [c.get_entity_transform(d, i)
+               for c, d, i in zip(self.cells, dim, alpha)]
+
+        slices = TensorProductCell._split_slices(dim)
+
+        def transform(point):
+            return list(chain(*[t(point[s])
+                                for t, s in zip(sct, slices)]))
+        return transform
+
+    def volume(self):
+        """Computes the volume in the appropriate dimensional measure."""
+        return numpy.prod([c.volume() for c in self.cells])
+
+    def compute_reference_normal(self, facet_dim, facet_i):
+        """Returns the unit normal in infinity norm to facet_i of
+        subelement dimension facet_dim."""
+        assert len(facet_dim) == len(self.get_dimension())
+        indicator = numpy.array(self.get_dimension()) - numpy.array(facet_dim)
+        (cell_i,), = numpy.nonzero(indicator)
+
+        n = []
+        for i, c in enumerate(self.cells):
+            if cell_i == i:
+                n.extend(c.compute_reference_normal(facet_dim[i], facet_i))
+            else:
+                n.extend([0] * c.get_spatial_dimension())
+        return numpy.asarray(n)
+
+    def contains_point(self, point, epsilon=0):
+        """Checks if reference cell contains given point
+        (with numerical tolerance)."""
+        lengths = [c.get_spatial_dimension() for c in self.cells]
+        assert len(point) == sum(lengths)
+        slices = TensorProductCell._split_slices(lengths)
+        return reduce(operator.and_,
+                      (c.contains_point(point[s], epsilon=epsilon)
+                       for c, s in zip(self.cells, slices)),
+                      True)
+
+
+class FiredrakeQuadrilateral(Cell):
+    """This is the reference quadrilateral with vertices
+    (0.0, 0.0), (0.0, 1.0), (1.0, 0.0) and (1.0, 1.0)."""
+
+    def __init__(self):
+        product = TensorProductCell(UFCInterval(), UFCInterval())
+        pt = product.get_topology()
+
+        verts = product.get_vertices()
+        topology = {0: pt[(0, 0)],
+                    1: dict(enumerate(list(itervalues(pt[(0, 1)])) + list(itervalues(pt[(1, 0)])))),
+                    2: pt[(1, 1)]}
+        super(FiredrakeQuadrilateral, self).__init__(QUADRILATERAL, verts, topology)
+        self.product = product
+
+    def get_dimension(self):
+        """Returns the subelement dimension of the cell.  Same as the
+        spatial dimension."""
+        return self.get_spatial_dimension()
+
+    def construct_subelement(self, dimension):
+        """Constructs the reference element of a cell subentity
+        specified by subelement dimension.
+
+        :arg dimension: subentity dimension (integer)
+        """
+        if dimension == 2:
+            return self
+        elif dimension == 1:
+            return UFCInterval()
+        elif dimension == 0:
+            return Point()
+        else:
+            raise ValueError("Invalid dimension: %d" % (dimension,))
+
+    def get_entity_transform(self, dim, entity_i):
+        """Returns a mapping of point coordinates from the
+        `entity_i`-th subentity of dimension `dim` to the cell.
 
-def make_affine_mapping( xs, ys ):
+        :arg dim: entity dimension (integer)
+        :arg entity_i: entity number (integer)
+        """
+        d, e = {0: lambda e: ((0, 0), e),
+                1: lambda e: {0: ((0, 1), 0),
+                              1: ((0, 1), 1),
+                              2: ((1, 0), 0),
+                              3: ((1, 0), 1)}[e],
+                2: lambda e: ((1, 1), e)}[dim](entity_i)
+        return self.product.get_entity_transform(d, e)
+
+    def volume(self):
+        """Computes the volume in the appropriate dimensional measure."""
+        return self.product.volume()
+
+    def compute_reference_normal(self, facet_dim, facet_i):
+        """Returns the unit normal in infinity norm to facet_i."""
+        assert facet_dim == 1
+        d, i = {0: ((0, 1), 0),
+                1: ((0, 1), 1),
+                2: ((1, 0), 0),
+                3: ((1, 0), 1)}[facet_i]
+        return self.product.compute_reference_normal(d, i)
+
+    def contains_point(self, point, epsilon=0):
+        """Checks if reference cell contains given point
+        (with numerical tolerance)."""
+        return self.product.contains_point(point, epsilon=epsilon)
+
+
+def make_affine_mapping(xs, ys):
     """Constructs (A,b) such that x --> A * x + b is the affine
     mapping from the simplex defined by xs to the simplex defined by ys."""
 
-    dim_x = len( xs[0] )
-    dim_y = len( ys[0] )
+    dim_x = len(xs[0])
+    dim_y = len(ys[0])
 
-    if len( xs ) != len( ys ):
+    if len(xs) != len(ys):
         raise Exception("")
 
     # find A in R^{dim_y,dim_x}, b in R^{dim_y} such that
     # A xs[i] + b = ys[i] for all i
 
-    mat = numpy.zeros( (dim_x*dim_y+dim_y, dim_x*dim_y+dim_y), "d" )
-    rhs = numpy.zeros( (dim_x*dim_y+dim_y,), "d" )
+    mat = numpy.zeros((dim_x * dim_y + dim_y, dim_x * dim_y + dim_y), "d")
+    rhs = numpy.zeros((dim_x * dim_y + dim_y,), "d")
 
     # loop over points
-    for i in range( len( xs ) ):
+    for i in range(len(xs)):
         # loop over components of each A * point + b
-        for j in range( dim_y ):
-            row_cur = i*dim_y+j
+        for j in range(dim_y):
+            row_cur = i * dim_y + j
             col_start = dim_x * j
             col_finish = col_start + dim_x
-            mat[row_cur, col_start:col_finish] = numpy.array( xs[i] )
+            mat[row_cur, col_start:col_finish] = numpy.array(xs[i])
             rhs[row_cur] = ys[i][j]
             # need to get terms related to b
-            mat[row_cur, dim_y*dim_x+j] = 1.0
+            mat[row_cur, dim_y * dim_x + j] = 1.0
 
-    sol = numpy.linalg.solve( mat, rhs )
+    sol = numpy.linalg.solve(mat, rhs)
 
-    A = numpy.reshape( sol[:dim_x*dim_y], (dim_y, dim_x) )
-    b = sol[dim_x*dim_y:]
+    A = numpy.reshape(sol[:dim_x * dim_y], (dim_y, dim_x))
+    b = sol[dim_x * dim_y:]
 
     return A, b
 
 
-
-def default_simplex( spatial_dim ):
+def default_simplex(spatial_dim):
     """Factory function that maps spatial dimension to an instance of
     the default reference simplex of that dimension."""
     if spatial_dim == 1:
@@ -476,55 +883,68 @@ def default_simplex( spatial_dim ):
         return DefaultTriangle()
     elif spatial_dim == 3:
         return DefaultTetrahedron()
+    else:
+        raise RuntimeError("Can't create default simplex of dimension %s." % str(spatial_dim))
 
-def ufc_simplex( spatial_dim ):
+
+def ufc_simplex(spatial_dim):
     """Factory function that maps spatial dimension to an instance of
     the UFC reference simplex of that dimension."""
-    if spatial_dim == 1:
+    if spatial_dim == 0:
+        return Point()
+    elif spatial_dim == 1:
         return UFCInterval()
     elif spatial_dim == 2:
         return UFCTriangle()
     elif spatial_dim == 3:
         return UFCTetrahedron()
     else:
-        raise RuntimeError("Don't know how to create UFC simplex for dimension %s" % str(spatial_dim))
+        raise RuntimeError("Can't create UFC simplex of dimension %s." % str(spatial_dim))
+
+
+def ufc_cell(cell):
+    """Handle incoming calls from FFC."""
+
+    # celltype could be a string or a cell.
+    if isinstance(cell, string_types):
+        celltype = cell
+    else:
+        celltype = cell.cellname()
+
+    if " * " in celltype:
+        # Tensor product cell
+        return TensorProductCell(*map(ufc_cell, celltype.split(" * ")))
+    elif celltype == "quadrilateral":
+        return FiredrakeQuadrilateral()
+    elif celltype == "interval":
+        return ufc_simplex(1)
+    elif celltype == "triangle":
+        return ufc_simplex(2)
+    elif celltype == "tetrahedron":
+        return ufc_simplex(3)
+    else:
+        raise RuntimeError("Don't know how to create UFC cell of type %s" % str(celltype))
 
-def volume( verts ):
+
+def volume(verts):
     """Constructs the volume of the simplex spanned by verts"""
-    from .factorial import factorial
+
     # use fact that volume of UFC reference element is 1/n!
-    sd = len( verts ) - 1
-    ufcel = ufc_simplex( sd )
+    sd = len(verts) - 1
+    ufcel = ufc_simplex(sd)
     ufcverts = ufcel.get_vertices()
 
-    A, b = make_affine_mapping( ufcverts, verts )
+    A, b = make_affine_mapping(ufcverts, verts)
 
     # can't just take determinant since, e.g. the face of
     # a tet being mapped to a 2d triangle doesn't have a
     # square matrix
 
-    (u, s, vt) = numpy.linalg.svd( A )
+    (u, s, vt) = numpy.linalg.svd(A)
 
     # this is the determinant of the "square part" of the matrix
     # (ie the part that maps the restriction of the higher-dimensional
     # stuff to UFC element
-    p = numpy.prod( [ si for si in s if (si) > 1.e-10 ] )
-
-    return p / factorial( sd )
-
-if __name__ == "__main__":
-#    U = UFCTetrahedron()
-#    print U.make_points( 1 , 1 , 3 )
-#    for i in range(len(U.vertices)):
-#        print U.compute_normal( i )
-
-    V = DefaultTetrahedron()
-    sd = V.get_spatial_dimension()
-
-#    print make_affine_mapping(V.get_vertices(),U.get_vertices())
+    p = numpy.prod([si for si in s if (si) > 1.e-10])
 
-    for i in range( len( V.vertices ) ):
-        print(V.compute_normal( i ))
-        print(V.compute_scaled_normal( i ))
-        print(volume( V.get_vertices_of_subcomplex( V.topology[sd-1][i] ) ))
-        print()
+    return p / factorial(sd)
diff --git a/FIAT/regge.py b/FIAT/regge.py
index feebf85..206557c 100644
--- a/FIAT/regge.py
+++ b/FIAT/regge.py
@@ -1,4 +1,7 @@
-# Copyright (C) 2015-2017 Lizao Li
+# -*- coding: utf-8 -*-
+"""Implementation of the generalized Regge finite elements."""
+
+# Copyright (C) 2015-2018 Lizao Li
 #
 # This file is part of FIAT.
 #
@@ -14,204 +17,92 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import absolute_import, print_function, division
 
-import numpy
+from FIAT.finite_element import CiarletElement
+from FIAT.dual_set import DualSet
+from FIAT.polynomial_set import ONSymTensorPolynomialSet
+from FIAT.functional import PointwiseInnerProductEvaluation as InnerProduct
 
-from .finite_element import FiniteElement
-from .dual_set import DualSet
-from .polynomial_set import ONSymTensorPolynomialSet
-from .functional import PointwiseInnerProductEvaluation as InnerProduct
-from .functional import index_iterator
-from .reference_element import UFCTriangle, UFCTetrahedron
 
 class ReggeDual(DualSet):
-    """
-    """
-    def __init__ (self, cell, degree):
-        (dofs, ids) = self.generate_degrees_of_freedom(cell, degree)
-        DualSet.__init__(self, dofs, cell, ids)
+    """Degrees of freedom for generalized Regge finite elements."""
+    def __init__(self, cell, degree):
+        dim = cell.get_spatial_dimension()
+        if (dim < 2) or (dim > 3):
+            raise ValueError("Generalized Regge elements are implemented only "
+                             "for dimension 2--3. For 1D, it is just DG(r).")
 
-    def generate_degrees_of_freedom(self, cell, degree):
-        """ 
-        Suppose f is a k-face of the reference n-cell. Let t1,...,tk be a
-        basis for the tangent space of f as n-vectors. Given a symmetric
-        2-tensor field u on Rn. One set of dofs for Regge(r) on f is
-        the moment of each of the (k+1)k/2 scalar functions 
-          [u(t1,t1),u(t1,t2),...,u(t1,tk), 
-           u(t2,t2), u(t2,t3),...,..., u(tk,tk)] 
-        aginst scalar polynomials of degrees (r-k+1). Here this is
-        implemented as pointwise evaluations of those scalar functions.
+        # construct the degrees of freedoms
+        dofs = []               # list of functionals
+        # dof_ids[i][j] contains the indices of dofs that are associated with
+        # entity j in dim i
+        dof_ids = {}
 
-        Below is an implementation for dimension 2--3. In dimension 1, 
-        Regge(r)=DG(r). It is awkward in the current FEniCS interface to
-        implement the element uniformly for all dimensions due to its edge,
-        facet=trig, cell style.
-        """
-        
-        dofs = []
-        ids = {}
-        
-        top = cell.get_topology()
-        d = cell.get_spatial_dimension()
-        if (d < 2) or (d > 3):
-            raise("Regge elements only implemented for dimension 2--3.")
-        
-        # No vertex dof
-        ids[0] = dict(list(zip(list(range(d+1)), ([] for i in range(d+1)))))
+        # no vertex dof
+        dof_ids[0] = {i: [] for i in range(dim + 1)}
         # edge dofs
-        (_dofs, _ids) = self._generate_edge_dofs(cell, degree, 0)
+        (_dofs, _dof_ids) = self._generate_dofs(cell, 1, degree, 0)
         dofs.extend(_dofs)
-        ids[1] = _ids
+        dof_ids[1] = _dof_ids
         # facet dofs for 3D
-        if d == 3:
-            (_dofs, _ids) = self._generate_facet_dofs(cell, degree, len(dofs))
+        if dim == 3:
+            (_dofs, _dof_ids) = self._generate_dofs(cell, 2, degree, len(dofs))
             dofs.extend(_dofs)
-            ids[2] = _ids
-        # Cell dofs
-        (_dofs, _ids) = self._generate_cell_dofs(cell, degree, len(dofs))
+            dof_ids[2] = _dof_ids
+        # cell dofs
+        (_dofs, _dof_ids) = self._generate_dofs(cell, dim, degree, len(dofs))
         dofs.extend(_dofs)
-        ids[d] = _ids
-        return (dofs, ids)
+        dof_ids[dim] = _dof_ids
 
-    def _generate_edge_dofs(self, cell, degree, offset):
-        """Generate dofs on edges."""
-        dofs = []
-        ids = {}
-        for s in range(len(cell.get_topology()[1])):
-            # Points to evaluate the inner product            
-            pts = cell.make_points(1, s, degree + 2)
-            # Evalute squared length of the tagent vector along an edge
-            t = cell.compute_edge_tangent(s)
-            # Fill dofs
-            dofs += [InnerProduct(cell, t, t, p) for p in pts]
-            # Fill ids
-            i = len(pts) * s
-            ids[s] = list(range(offset + i, offset + i + len(pts)))
-        return (dofs, ids)
+        super(ReggeDual, self).__init__(dofs, cell, dof_ids)
 
-    def _generate_facet_dofs(self, cell, degree, offset):
-        """Generate dofs on facets in 3D."""
-        # Return empty if there is no such dofs
-        dofs = []
-        d = cell.get_spatial_dimension()
-        ids = dict(list(zip(list(range(4)), ([] for i in range(4)))))
-        if degree == 0:
-            return (dofs, ids)
-        # Compute dofs
-        for s in range(len(cell.get_topology()[2])):
-            # Points to evaluate the inner product
-            pts = cell.make_points(2, s, degree + 2)
-            # Let t1 and t2 be the two tangent vectors along a triangle
-            # we evaluate u(t1,t1), u(t1,t2), u(t2,t2) at each point.
-            (t1, t2) = cell.compute_face_tangents(s)
-            # Fill dofs
-            for p in pts:
-                dofs += [InnerProduct(cell, t1, t1, p),
-                         InnerProduct(cell, t1, t2, p),
-                         InnerProduct(cell, t2, t2, p)]
-            # Fill ids
-            i = len(pts) * s * 3
-            ids[s] = list(range(offset + i, offset + i + len(pts) * 3))
-        return (dofs, ids)
+    @staticmethod
+    def _generate_dofs(cell, entity_dim, degree, offset):
+        """generate degrees of freedom for enetities of dimension entity_dim
 
-    def _generate_cell_dofs(self, cell, degree, offset):
-        """Generate dofs for cells."""
-        # Return empty if there is no such dofs
+        Input: all obvious except
+           offset  -- the current first available dof id.
+
+        Output:
+           dofs    -- an array of dofs associated to entities in that dim
+           dof_ids -- a dict mapping entity_id to the range of indices of dofs
+                      associated to it.
+
+        On a k-face for degree r, the dofs are given by the value of
+           t^T u t
+        evaluated at points enough to control P(r-k+1) for all the edge
+        tangents of the face.
+        `cell.make_points(entity_dim, entity_id, degree + 2)` happens to
+        generate exactly those points needed.
+        """
         dofs = []
-        d = cell.get_spatial_dimension()
-        if (d == 2 and degree == 0) or (d == 3 and degree <= 1):
-            return ([], {0: []})
-        # Compute dofs. There is only one cell. So no need to loop here~ 
-        # Points to evaluate the inner product
-        pts = cell.make_points(d, 0, degree + 2)
-        # Let {e1,..,ek} be the Euclidean basis. We evaluate inner products
-        #   u(e1,e1), u(e1,e2), u(e1,e3), u(e2,e2), u(e2,e3),..., u(ek,ek)
-        # at each point.
-        e = numpy.eye(d)
-        # Fill dofs
-        for p in pts:
-            dofs += [InnerProduct(cell, e[i], e[j], p)
-                     for [i,j] in index_iterator((d, d)) if i <= j]
-        # Fill ids
-        ids = {0 :
-               list(range(offset, offset + len(pts) * d * (d + 1) // 2))}
-        return (dofs, ids)
+        dof_ids = {}
+        num_entities = len(cell.get_topology()[entity_dim])
+        for entity_id in range(num_entities):
+            pts = cell.make_points(entity_dim, entity_id, degree + 2)
+            tangents = cell.compute_face_edge_tangents(entity_dim, entity_id)
+            dofs += [InnerProduct(cell, t, t, pt)
+                     for pt in pts
+                     for t in tangents]
+            num_new_dofs = len(pts) * len(tangents)
+            dof_ids[entity_id] = list(range(offset, offset + num_new_dofs))
+            offset += num_new_dofs
+        return (dofs, dof_ids)
 
-class Regge(FiniteElement):
-    """
-    The Regge elements on triangles and tetrahedra: the polynomial space
-    described by the full polynomials of degree k with degrees of freedom
-    to ensure its pullback as a metric to each interior facet and edge is
-    single-valued.
-    """
 
+class Regge(CiarletElement):
+    """The generalized Regge elements for symmetric-matrix-valued functions.
+       REG(r) in dimension n is the space of polynomial symmetric-matrix-valued
+       functions of degree r or less with tangential-tangential continuity.
+    """
     def __init__(self, cell, degree):
-        # Check degree
-        assert(degree >= 0), "Regge start at degree 0!"
-        # Get dimension
-        d = cell.get_spatial_dimension()
-        # Construct polynomial basis for d-vector fields
+        assert degree >= 0, "Regge start at degree 0!"
+        # shape functions
         Ps = ONSymTensorPolynomialSet(cell, degree)
-        # Construct dual space
+        # degrees of freedom
         Ls = ReggeDual(cell, degree)
-        # Set mapping
-        mapping = "pullback as metric"
-        # Call init of super-class
-        FiniteElement.__init__(self, Ps, Ls, degree, mapping=mapping)
-
-if __name__=="__main__":
-    print("Test 0: Regge degree 0 in 2D.")
-    T = UFCTriangle()
-    R = Regge(T, 0)
-    print("-----")
-    pts = numpy.array([[0.0, 0.0]])
-    ts = numpy.array([[0.0, 1.0],
-                      [1.0, 0.0],
-                      [-1.0, 1.0]])
-    vals = R.tabulate(0, pts)[(0, 0)]
-    for i in range(R.space_dimension()):
-        print("Basis #{}:".format(i))
-        for j in range(len(pts)):
-            tut = [t.dot(vals[i, :, :, j].dot(t)) for t in ts]
-            print("u(t,t) for edge tagents t at {} are: {}".format(
-                pts[j], tut))
-    print("-----")
-    print("Expected result: a single 1 for each basis and zeros for others.")
-    print("")
-
-    print("Test 1: Regge degree 0 in 3D.")
-    T = UFCTetrahedron()
-    R = Regge(T, 0)
-    print("-----")
-    pts = numpy.array([[0.0, 0.0, 0.0]])
-    ts = numpy.array([[1.0, 0.0, 0.0],
-                      [0.0, 1.0, 0.0],
-                      [0.0, 0.0, 1.0],
-                      [1.0, -1.0, 0.0],
-                      [1.0, 0.0, -1.0],
-                      [0.0, 1.0, -1.0]])
-    vals = R.tabulate(0, pts)[(0, 0, 0)]
-    for i in range(R.space_dimension()):
-        print("Basis #{}:".format(i))
-        for j in range(len(pts)):
-            tut = [t.dot(vals[i, :, :, j].dot(t)) for t in ts]
-            print("u(t,t) for edge tagents t at {} are: {}".format(
-                pts[j], tut))
-    print("-----")
-    print("Expected result: a single 1 for each basis and zeros for others.")
-    print("")
+        # mapping under affine transformation
+        mapping = "double covariant piola"
 
-    print("Test 2: association of dofs to mesh entities.")
-    print("------")
-    for k in range(0, 3):
-        print("Degree {} in 2D:".format(k))
-        T = UFCTriangle()
-        R = Regge(T, k)
-        print(R.entity_dofs())
-        print("")
-    for k in range(0, 3):
-        print("Degree {} in 3D:".format(k))
-        T = UFCTetrahedron()
-        R = Regge(T, k)
-        print(R.entity_dofs())        
-        print("")
+        super(Regge, self).__init__(Ps, Ls, degree, mapping=mapping)
diff --git a/FIAT/restricted.py b/FIAT/restricted.py
new file mode 100644
index 0000000..ea71a5c
--- /dev/null
+++ b/FIAT/restricted.py
@@ -0,0 +1,126 @@
+# Copyright (C) 2015-2016 Jan Blechta, Andrew T T McRae, and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import six
+from six import string_types
+from six import iteritems
+from FIAT.dual_set import DualSet
+from FIAT.finite_element import CiarletElement
+
+
+class RestrictedElement(CiarletElement):
+    """Restrict given element to specified list of dofs."""
+
+    def __init__(self, element, indices=None, restriction_domain=None):
+        '''For sake of argument, indices overrides restriction_domain'''
+
+        if not (indices or restriction_domain):
+            raise RuntimeError("Either indices or restriction_domain must be passed in")
+
+        if not indices:
+            indices = _get_indices(element, restriction_domain)
+
+        if isinstance(indices, string_types):
+            raise RuntimeError("variable 'indices' was a string; did you forget to use a keyword?")
+
+        if len(indices) == 0:
+            raise ValueError("No point in creating empty RestrictedElement.")
+
+        self._element = element
+        self._indices = indices
+
+        # Fetch reference element
+        ref_el = element.get_reference_element()
+
+        # Restrict primal set
+        poly_set = element.get_nodal_basis().take(indices)
+
+        # Restrict dual set
+        dof_counter = 0
+        entity_ids = {}
+        nodes = []
+        nodes_old = element.dual_basis()
+        for d, entities in six.iteritems(element.entity_dofs()):
+            entity_ids[d] = {}
+            for entity, dofs in six.iteritems(entities):
+                entity_ids[d][entity] = []
+                for dof in dofs:
+                    if dof not in indices:
+                        continue
+                    entity_ids[d][entity].append(dof_counter)
+                    dof_counter += 1
+                    nodes.append(nodes_old[dof])
+        assert dof_counter == len(indices)
+        dual = DualSet(nodes, ref_el, entity_ids)
+
+        # Restrict mapping
+        mapping_old = element.mapping()
+        mapping_new = [mapping_old[dof] for dof in indices]
+        assert all(e_mapping == mapping_new[0] for e_mapping in mapping_new)
+
+        # Call constructor of CiarletElement
+        super(RestrictedElement, self).__init__(poly_set, dual, 0, element.get_formdegree(), mapping_new[0])
+
+
+def sorted_by_key(mapping):
+    "Sort dict items by key, allowing different key types."
+    # Python3 doesn't allow comparing builtins of different type, therefore the typename trick here
+    def _key(x):
+        return (type(x[0]).__name__, x[0])
+    return sorted(iteritems(mapping), key=_key)
+
+
+def _get_indices(element, restriction_domain):
+    "Restriction domain can be 'interior', 'vertex', 'edge', 'face' or 'facet'"
+
+    if restriction_domain == "interior":
+        # Return dofs from interior
+        return element.entity_dofs()[max(element.entity_dofs().keys())][0]
+
+    # otherwise return dofs with d <= dim
+    if restriction_domain == "vertex":
+        dim = 0
+    elif restriction_domain == "edge":
+        dim = 1
+    elif restriction_domain == "face":
+        dim = 2
+    elif restriction_domain == "facet":
+        dim = element.get_reference_element().get_spatial_dimension() - 1
+    else:
+        raise RuntimeError("Invalid restriction domain")
+
+    is_prodcell = isinstance(max(element.entity_dofs().keys()), tuple)
+
+    entity_dofs = element.entity_dofs()
+    indices = []
+    for d in range(dim + 1):
+        if is_prodcell:
+            for a in range(d + 1):
+                b = d - a
+                try:
+                    entities = entity_dofs[(a, b)]
+                    for (entity, index) in sorted_by_key(entities):
+                        indices += index
+                except KeyError:
+                    pass
+        else:
+            entities = entity_dofs[d]
+            for (entity, index) in sorted_by_key(entities):
+                indices += index
+    return indices
diff --git a/FIAT/tabarg.py b/FIAT/tabarg.py
deleted file mode 100644
index 4d3205d..0000000
--- a/FIAT/tabarg.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from . import argyris, reference_element
-
-degree = 5
-lattice_size = 10 * degree
-
-T = reference_element.DefaultTriangle()
-
-U = argyris.QuinticArgyris(T)
-pts = T.make_lattice( lattice_size )
-
-bfvals = U.get_nodal_basis().tabulate_new( pts )
-u0 = bfvals[0]
-fout = open("arg0.dat", "w")
-for i in range(len(pts)):
-    fout.write("%s %s %s\n" % (pts[i][0], pts[i][1], u0[i]))
-fout.close()
-
-u1 = bfvals[1]
-fout = open("arg1.dat", "w")
-for i in range(len(pts)):
-    fout.write("%s %s %s\n" % (pts[i][0], pts[i][1], u1[i]))
-fout.close()
-
-u2 = bfvals[3]
-fout = open("arg2.dat", "w")
-for i in range(len(pts)):
-    fout.write("%s %s %s\n" % (pts[i][0], pts[i][1], u2[i]))
-fout.close()
-
-u3 = bfvals[18]
-fout = open("arg3.dat", "w")
-for i in range(len(pts)):
-    fout.write("%s %s %s\n" % (pts[i][0], pts[i][1], u3[i]))
-fout.close()
diff --git a/FIAT/tablag.py b/FIAT/tablag.py
deleted file mode 100644
index d8ba3ee..0000000
--- a/FIAT/tablag.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from . import shapes, Lagrange
-
-shape = 3
-degree = 3
-lattice_size = 10 * degree
-
-U = Lagrange.Lagrange(shape, degree)
-pts = shapes.make_lattice(shape, lattice_size)
-
-us = U.function_space().tabulate(pts)
-
-fout = open("foo.dat", "w")
-u0 = us[0]
-for i in range(len(pts)):
-    fout.write("%s %s %s %s" % (pts[i][0], pts[i][1], pts[i][2], u0[i]))
-fout.close()
diff --git a/FIAT/tensor_product.py b/FIAT/tensor_product.py
new file mode 100644
index 0000000..bdcdcc9
--- /dev/null
+++ b/FIAT/tensor_product.py
@@ -0,0 +1,368 @@
+# Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Copyright (C) 2013 Andrew T. T. McRae
+# Modified by Thomas H. Gibson, 2016
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import numpy
+from FIAT.finite_element import FiniteElement
+from FIAT.reference_element import TensorProductCell
+from FIAT.polynomial_set import mis
+from FIAT import dual_set
+from FIAT import functional
+
+
+def _first_point(node):
+    return tuple(node.get_point_dict().keys())[0]
+
+
+def _first_point_pair(node):
+    return tuple(node.get_point_dict().items())[0]
+
+
+class TensorProductElement(FiniteElement):
+    """Class implementing a finite element that is the tensor product
+    of two existing finite elements."""
+
+    def __init__(self, A, B):
+        # set up simple things
+        order = min(A.get_order(), B.get_order())
+        if A.get_formdegree() is None or B.get_formdegree() is None:
+            formdegree = None
+        else:
+            formdegree = A.get_formdegree() + B.get_formdegree()
+
+        # set up reference element
+        ref_el = TensorProductCell(A.get_reference_element(),
+                                   B.get_reference_element())
+
+        if A.mapping()[0] != "affine" and B.mapping()[0] == "affine":
+            mapping = A.mapping()[0]
+        elif B.mapping()[0] != "affine" and A.mapping()[0] == "affine":
+            mapping = B.mapping()[0]
+        elif A.mapping()[0] == "affine" and B.mapping()[0] == "affine":
+            mapping = "affine"
+        else:
+            raise ValueError("check tensor product mappings - at least one must be affine")
+
+        # set up entity_ids
+        Adofs = A.entity_dofs()
+        Bdofs = B.entity_dofs()
+        Bsdim = B.space_dimension()
+        entity_ids = {}
+
+        for curAdim in Adofs:
+            for curBdim in Bdofs:
+                entity_ids[(curAdim, curBdim)] = {}
+                dim_cur = 0
+                for entityA in Adofs[curAdim]:
+                    for entityB in Bdofs[curBdim]:
+                        entity_ids[(curAdim, curBdim)][dim_cur] = \
+                            [x*Bsdim + y for x in Adofs[curAdim][entityA]
+                                for y in Bdofs[curBdim][entityB]]
+                        dim_cur += 1
+
+        # set up dual basis
+        Anodes = A.dual_basis()
+        Bnodes = B.dual_basis()
+
+        # build the dual set by inspecting the current dual
+        # sets item by item.
+        # Currently supported cases:
+        # PointEval x PointEval = PointEval [scalar x scalar = scalar]
+        # PointScaledNormalEval x PointEval = PointScaledNormalEval [vector x scalar = vector]
+        # ComponentPointEvaluation x PointEval [vector x scalar = vector]
+        nodes = []
+        for Anode in Anodes:
+            if isinstance(Anode, functional.PointEvaluation):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: PointEval x PointEval
+                        # the PointEval functional just requires the
+                        # coordinates. these are currently stored as
+                        # the key of a one-item dictionary. we retrieve
+                        # these by calling get_point_dict(), and
+                        # use the concatenation to make a new PointEval
+                        nodes.append(functional.PointEvaluation(ref_el, _first_point(Anode) + _first_point(Bnode)))
+                    elif isinstance(Bnode, functional.IntegralMoment):
+                        # dummy functional for product with integral moments
+                        nodes.append(functional.Functional(None, None, None,
+                                                           {}, "Undefined"))
+                    elif isinstance(Bnode, functional.PointDerivative):
+                        # dummy functional for product with point derivative
+                        nodes.append(functional.Functional(None, None, None,
+                                                           {}, "Undefined"))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.PointScaledNormalEvaluation):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: PointScaledNormalEval x PointEval
+                        # this could be wrong if the second shape
+                        # has spatial dimension >1, since we are not
+                        # explicitly scaling by facet size
+                        if len(_first_point(Bnode)) > 1:
+                            # TODO: support this case one day
+                            raise NotImplementedError("PointScaledNormalEval x PointEval is not yet supported if the second shape has dimension > 1")
+                        # We cannot make a new functional.PSNEval in
+                        # the natural way, since it tries to compute
+                        # the normal vector by itself.
+                        # Instead, we create things manually, and
+                        # call Functional() with these arguments
+                        sd = ref_el.get_spatial_dimension()
+                        # The pt_dict is a one-item dictionary containing
+                        # the details of the functional.
+                        # The key is the spatial coordinate, which
+                        # is just a concatenation of the two parts.
+                        # The value is a list of tuples, representing
+                        # the normal vector (scaled by the volume of
+                        # the facet) at that point.
+                        # Each tuple looks like (foo, (i,)); the i'th
+                        # component of the scaled normal is foo.
+
+                        # The following line is only valid when the second
+                        # shape has spatial dimension 1 (enforced above)
+                        Apoint, Avalue = _first_point_pair(Anode)
+                        pt_dict = {Apoint + _first_point(Bnode): Avalue + [(0.0, (len(Apoint),))]}
+
+                        # The following line should be used in the
+                        # general case
+                        # pt_dict = {Anode.get_point_dict().keys()[0] + Bnode.get_point_dict().keys()[0]: Anode.get_point_dict().values()[0] + [(0.0, (ii,)) for ii in range(len(Anode.get_point_dict().keys()[0]), len(Anode.get_point_dict().keys()[0]) + len(Bnode.get_point_dict().keys()[0]))]}
+
+                        # THE FOLLOWING IS PROBABLY CORRECT BUT UNTESTED
+                        shp = (sd,)
+                        nodes.append(functional.Functional(ref_el, shp, pt_dict, {}, "PointScaledNormalEval"))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.PointEdgeTangentEvaluation):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: PointEdgeTangentEval x PointEval
+                        # this is very similar to the case above, so comments omitted
+                        if len(_first_point(Bnode)) > 1:
+                            raise NotImplementedError("PointEdgeTangentEval x PointEval is not yet supported if the second shape has dimension > 1")
+                        sd = ref_el.get_spatial_dimension()
+                        Apoint, Avalue = _first_point_pair(Anode)
+                        pt_dict = {Apoint + _first_point(Bnode): Avalue + [(0.0, (len(Apoint),))]}
+
+                        # THE FOLLOWING IS PROBABLY CORRECT BUT UNTESTED
+                        shp = (sd,)
+                        nodes.append(functional.Functional(ref_el, shp, pt_dict, {}, "PointEdgeTangent"))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.ComponentPointEvaluation):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: ComponentPointEval x PointEval
+                        # the CptPointEval functional requires the component
+                        # and the coordinates. very similar to PE x PE case.
+                        sd = ref_el.get_spatial_dimension()
+                        nodes.append(functional.ComponentPointEvaluation(ref_el, Anode.comp, (sd,), _first_point(Anode) + _first_point(Bnode)))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.FrobeniusIntegralMoment):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: FroIntMom x PointEval
+                        sd = ref_el.get_spatial_dimension()
+                        pt_dict = {}
+                        pt_old = Anode.get_point_dict()
+                        for pt in pt_old:
+                            pt_dict[pt+_first_point(Bnode)] = pt_old[pt] + [(0.0, sd-1)]
+                        # THE FOLLOWING IS PROBABLY CORRECT BUT UNTESTED
+                        shp = (sd,)
+                        nodes.append(functional.Functional(ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment"))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.IntegralMoment):
+                for Bnode in Bnodes:
+                    if isinstance(Bnode, functional.PointEvaluation):
+                        # case: IntMom x PointEval
+                        sd = ref_el.get_spatial_dimension()
+                        pt_dict = {}
+                        pt_old = Anode.get_point_dict()
+                        for pt in pt_old:
+                            pt_dict[pt+_first_point(Bnode)] = pt_old[pt]
+                        # THE FOLLOWING IS PROBABLY CORRECT BUT UNTESTED
+                        shp = (sd,)
+                        nodes.append(functional.Functional(ref_el, shp, pt_dict, {}, "IntegralMoment"))
+                    else:
+                        raise NotImplementedError("unsupported functional type")
+
+            elif isinstance(Anode, functional.Functional):
+                # this should catch everything else
+                for Bnode in Bnodes:
+                    nodes.append(functional.Functional(None, None, None, {}, "Undefined"))
+            else:
+                raise NotImplementedError("unsupported functional type")
+
+        dual = dual_set.DualSet(nodes, ref_el, entity_ids)
+
+        super(TensorProductElement, self).__init__(ref_el, dual, order, formdegree, mapping)
+        # Set up constituent elements
+        self.A = A
+        self.B = B
+
+        # degree for quadrature rule
+        self.polydegree = max(A.degree(), B.degree())
+
+    def degree(self):
+        """Return the degree of the (embedding) polynomial space."""
+        return self.polydegree
+
+    def get_nodal_basis(self):
+        """Return the nodal basis, encoded as a PolynomialSet object,
+        for the finite element."""
+        raise NotImplementedError("get_nodal_basis not implemented")
+
+    def get_coeffs(self):
+        """Return the expansion coefficients for the basis of the
+        finite element."""
+        raise NotImplementedError("get_coeffs not implemented")
+
+    def tabulate(self, order, points, entity=None):
+        """Return tabulated values of derivatives up to given order of
+        basis functions at given points."""
+        if entity is None:
+            entity = (self.ref_el.get_dimension(), 0)
+        entity_dim, entity_id = entity
+
+        shape = tuple(len(c.get_topology()[d])
+                      for c, d in zip(self.ref_el.cells, entity_dim))
+        idA, idB = numpy.unravel_index(entity_id, shape)
+
+        # Factor the entity argument to get entities of the component elements
+        entityA_dim, entityB_dim = entity_dim
+        entityA = (entityA_dim, idA)
+        entityB = (entityB_dim, idB)
+
+        pointsAdim, pointsBdim = [c.get_spatial_dimension()
+                                  for c in self.ref_el.construct_subelement(entity_dim).cells]
+        pointsA = [point[:pointsAdim] for point in points]
+        pointsB = [point[pointsAdim:pointsAdim + pointsBdim] for point in points]
+
+        Asdim = self.A.ref_el.get_spatial_dimension()
+        Bsdim = self.B.ref_el.get_spatial_dimension()
+        # Note that for entities other than cells, the following
+        # tabulations are already appropriately zero-padded so no
+        # additional zero padding is required.
+        Atab = self.A.tabulate(order, pointsA, entityA)
+        Btab = self.B.tabulate(order, pointsB, entityB)
+        npoints = len(points)
+
+        # allow 2 scalar-valued FE spaces, or 1 scalar-valued,
+        # 1 vector-valued. Combining 2 vector-valued spaces
+        # into a tensor-valued space via an outer-product
+        # seems to be a sensible general option, but I don't
+        # know how to handle the nestedness of the arrays
+        # if someone then tries to make a new "tensor finite
+        # element" where one component is already a
+        # tensor-valued space!
+        A_valuedim = len(self.A.value_shape())  # scalar: 0, vector: 1
+        B_valuedim = len(self.B.value_shape())  # scalar: 0, vector: 1
+        if A_valuedim + B_valuedim > 1:
+            raise NotImplementedError("tabulate does not support two vector-valued inputs")
+        result = {}
+        for i in range(order + 1):
+            alphas = mis(Asdim+Bsdim, i)  # thanks, Rob!
+            for alpha in alphas:
+                if A_valuedim == 0 and B_valuedim == 0:
+                    # for each point, get outer product of (A's basis
+                    # functions f1, f2, ... evaluated at that point)
+                    # with (B's basis functions g1, g2, ... evaluated
+                    # at that point). This gives temp[point][f_i][g_j].
+                    # Flatten this, so bfs are
+                    # in the order f1g1, f1g2, ..., f2g1, f2g2, ...
+                    # which is compatible with the entity_dofs order.
+                    # We now have temp[point][full basis function]
+                    # Transpose this to get temp[bf][point],
+                    # and we are done.
+                    temp = numpy.array([numpy.outer(
+                                       Atab[alpha[0:Asdim]][..., j],
+                                       Btab[alpha[Asdim:Asdim+Bsdim]][..., j])
+                        .ravel() for j in range(npoints)])
+                    result[alpha] = temp.transpose()
+                elif A_valuedim == 1 and B_valuedim == 0:
+                    # similar to above, except A's basis functions
+                    # are now vector-valued. numpy.outer flattens the
+                    # array, so it's like taking the OP of
+                    # f1_x, f1_y, f2_x, f2_y, ... with g1, g2, ...
+                    # this gives us
+                    # temp[point][f1x, f1y, f2x, f2y, ...][g_j].
+                    # reshape once to get temp[point][f_i][x/y][g_j]
+                    # transpose to get temp[point][x/y][f_i][g_j]
+                    # reshape to flatten the last two indices, this
+                    # gives us temp[point][x/y][full bf_i]
+                    # finally, transpose the first and last indices
+                    # to get temp[bf_i][x/y][point], and we are done.
+                    temp = numpy.array([numpy.outer(
+                                       Atab[alpha[0:Asdim]][..., j],
+                                       Btab[alpha[Asdim:Asdim+Bsdim]][..., j])
+                        for j in range(npoints)])
+                    assert temp.shape[1] % 2 == 0
+                    temp2 = temp.reshape((temp.shape[0],
+                                          temp.shape[1]//2,
+                                          2,
+                                          temp.shape[2]))\
+                        .transpose(0, 2, 1, 3)\
+                        .reshape((temp.shape[0], 2, -1))\
+                        .transpose(2, 1, 0)
+                    result[alpha] = temp2
+                elif A_valuedim == 0 and B_valuedim == 1:
+                    # as above, with B's functions now vector-valued.
+                    # we now do... [numpy.outer ... for ...] gives
+                    # temp[point][f_i][g1x,g1y,g2x,g2y,...].
+                    # reshape to temp[point][f_i][g_j][x/y]
+                    # flatten middle: temp[point][full bf_i][x/y]
+                    # transpose to temp[bf_i][x/y][point]
+                    temp = numpy.array([numpy.outer(
+                        Atab[alpha[0:Asdim]][..., j],
+                        Btab[alpha[Asdim:Asdim+Bsdim]][..., j])
+                        for j in range(len(Atab[alpha[0:Asdim]][0]))])
+                    assert temp.shape[2] % 2 == 0
+                    temp2 = temp.reshape((temp.shape[0], temp.shape[1],
+                                          temp.shape[2]//2, 2))\
+                        .reshape((temp.shape[0], -1, 2))\
+                        .transpose(1, 2, 0)
+                    result[alpha] = temp2
+        return result
+
+    def value_shape(self):
+        """Return the value shape of the finite element functions."""
+        if len(self.A.value_shape()) == 0 and len(self.B.value_shape()) == 0:
+            return ()
+        elif len(self.A.value_shape()) == 1 and len(self.B.value_shape()) == 0:
+            return (self.A.value_shape()[0],)
+        elif len(self.A.value_shape()) == 0 and len(self.B.value_shape()) == 1:
+            return (self.B.value_shape()[0],)
+        else:
+            raise NotImplementedError("value_shape not implemented")
+
+    def dmats(self):
+        """Return dmats: expansion coefficients for basis function
+        derivatives."""
+        raise NotImplementedError("dmats not implemented")
+
+    def get_num_members(self, arg):
+        """Return number of members of the expansion set."""
+        raise NotImplementedError("get_num_members not implemented")
diff --git a/FIAT/trace.py b/FIAT/trace.py
deleted file mode 100644
index 345e4b8..0000000
--- a/FIAT/trace.py
+++ /dev/null
@@ -1,269 +0,0 @@
-# Copyright (C) 2012-2015 Marie E. Rognes and David A. Ham
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import print_function
-
-import numpy
-
-from FIAT.discontinuous_lagrange import DiscontinuousLagrange
-from FIAT.reference_element import ufc_simplex
-from FIAT.functional import PointEvaluation
-from FIAT.polynomial_set import mis
-
-# Tolerance for geometry identifications
-epsilon = 1.e-8
-
-def extract_unique_facet(coordinates, tolerance=epsilon):
-    """Determine whether a set of points, each point described by its
-    barycentric coordinates ('coordinates'), are all on one of the
-    facets and return this facet and whether search has been
-    successful.
-    """
-    facets = []
-    for c in coordinates:
-        on_facet = set([i for (i, l) in enumerate(c) if abs(l) < tolerance])
-        facets += [on_facet]
-
-    unique_facet = facets[0]
-    for e in facets:
-        unique_facet = unique_facet & e
-
-    # Handle coordinates not on facets somewhat gracefully
-    if (len(unique_facet) != 1):
-        return (None, False)
-
-    # If we have a unique facet, return it and success
-    return (unique_facet.pop(), True)
-
-def barycentric_coordinates(points, vertices):
-    """Compute barycentric coordinates for a set of points ('points'),
-    relative to a simplex defined by a set of vertices ('vertices').
-    """
-
-    # Form map matrix
-    last = numpy.asarray(vertices[-1])
-    T = numpy.matrix([numpy.array(v) - last for v in vertices[:-1]]).T
-    detT = numpy.linalg.det(T)
-    invT = numpy.linalg.inv(T)
-
-    # Compute barycentric coordinates for all points
-    coords = []
-    for p in points:
-        y = numpy.asarray(p) - last
-        lam = invT.dot(y.T)
-        lam = [lam[(0, i)] for i in range(len(y))]
-        lam += [1.0 - sum(lam)]
-        coords.append(lam)
-    return coords
-
-def map_from_reference_facet(point, vertices):
-    """
-    Input:
-      vertices: the vertices defining the physical facet
-      point: the reference point to be mapped to the facet
-    """
-    # Compute barycentric coordinates of point relative to reference facet:
-    reference_simplex = ufc_simplex(len(vertices)-1)
-    reference_vertices = reference_simplex.get_vertices()
-    coords = barycentric_coordinates([point,], reference_vertices)[0]
-
-    # Evaluate physical coordinate of point using barycentric coordinates
-    point = sum(vertices[j]*coords[j] for j in range(len(coords)))
-
-    return tuple(point)
-
-def map_to_reference_facet(points, vertices, facet):
-    """Given a set of points in n D and a set of vertices describing a
-    facet of a simplex in n D (where the given points lie on this
-    facet) map the points to the reference simplex of dimension (n-1).
-    """
-
-    # Compute barycentric coordinates of points with respect to
-    # the full physical simplex
-    all_coords = barycentric_coordinates(points, vertices)
-
-    # Extract vertices of reference facet simplex
-    reference_facet_simplex = ufc_simplex(len(vertices)-2)
-    ref_vertices = reference_facet_simplex.get_vertices()
-
-    reference_points = []
-    for (i, coords) in enumerate(all_coords):
-        # Extract correct subset of barycentric coordinates since we
-        # know which facet we are on
-        new_coords = [coords[j] for j in range(len(coords)) if (j != facet)]
-
-        # Evaluate reference coordinate of point using revised
-        # barycentric coordinates
-        reference_pt = sum(numpy.asarray(ref_vertices[j])*new_coords[j]
-                           for j in range(len(new_coords)))
-
-        reference_points += [reference_pt]
-    return reference_points
-
-class DiscontinuousLagrangeTrace(object):
-    ""
-    def __init__(self, cell, k):
-
-        tdim = cell.get_spatial_dimension()
-        assert (tdim == 2 or tdim == 3)
-
-        # Store input cell and polynomial degree (k)
-        self.cell = cell
-        self.k = k
-
-        # Create DG_k space on the facet(s) of the cell
-        self.facet = ufc_simplex(tdim - 1)
-        self.DG = DiscontinuousLagrange(self.facet, k)
-
-        # Count number of facets for given cell. Assumption: we are on
-        # simplices
-        self.num_facets = tdim + 1
-
-        # Construct entity ids. Initialize all to empty, will fill
-        # later.
-        self.entity_ids = {}
-        topology = cell.get_topology()
-        for dim, entities in topology.items():
-            self.entity_ids[dim] = {}
-            for entity in entities:
-                self.entity_ids[dim][entity] = {}
-
-        # For each facet, we have dim(DG_k on that facet) number of dofs
-        n = self.DG.space_dimension()
-        for i in range(self.num_facets):
-            self.entity_ids[tdim-1][i] = range(i*n, (i+1)*n)
-
-    def degree(self):
-        return self.k
-
-    def value_shape(self):
-        return ()
-
-    def space_dimension(self):
-        """The space dimension of the trace space corresponds to the
-        DG space dimesion on each facet times the number of facets."""
-        return self.DG.space_dimension()*self.num_facets
-
-    def entity_dofs(self):
-        return self.entity_ids
-
-    def mapping(self):
-        return ["affine" for i in range(self.space_dimension())]
-
-    def dual_basis(self):
-
-        # First create the points
-        points = []
-
-        # For each facet, map the subcomplex DG_k dofs from the lower
-        # dimensional reference element onto the facet and add to list
-        # of points
-        DG_k_dual_basis = self.DG.dual_basis()
-        t_dim = self.cell.get_spatial_dimension()
-        facets2indices = self.cell.get_topology()[t_dim - 1]
-
-        # Iterate over the facets and add points on each facet
-        for (facet, indices) in facets2indices.items():
-            vertices = self.cell.get_vertices_of_subcomplex(indices)
-            vertices = numpy.array(vertices)
-            for dof in DG_k_dual_basis:
-                # PointEvaluation only carries one point
-                point = list(dof.get_point_dict().keys())[0]
-                pt = map_from_reference_facet([point,], vertices)
-                points.append(pt)
-
-        # One degree of freedom per point:
-        nodes = [PointEvaluation(self.cell, x) for x in points]
-        return nodes
-
-    def tabulate(self, order, points):
-        """Return tabulated values of derivatives up to given order of
-        basis functions at given points."""
-
-        # Standard derivatives don't make sense, but return zero
-        # because mixed elements compute all derivatives at once
-        if (order > 0):
-            values = {}
-            sdim = self.space_dimension()
-            alphas = mis(self.cell.get_spatial_dimension(), order)
-            for alpha in alphas:
-                values[alpha] = numpy.zeros(shape=(sdim, len(points)))
-            return values
-
-        # Identify which facet (if any) these points are on:
-        vertices = self.cell.vertices
-        coordinates = barycentric_coordinates(points, vertices)
-        (unique_facet, success) = extract_unique_facet(coordinates)
-
-        # All other basis functions evaluate to zero, so create an
-        # array of the right size
-        sdim = self.space_dimension()
-        values = numpy.zeros(shape=(sdim, len(points)))
-
-        # ... and plug in the non-zero values in just the right place
-        # if we found a unique facet
-        if success:
-
-            # Map point to "reference facet" (facet -> interval etc)
-            new_points = map_to_reference_facet(points, vertices, unique_facet)
-
-            # Call self.DG.tabulate(order, new_points) to compute the
-            # values of the points for the degrees of freedom on this facet
-            non_zeros = list(self.DG.tabulate(order, new_points).values())[0]
-            m = non_zeros.shape[0]
-            dg_dim = self.DG.space_dimension()
-            values[dg_dim*unique_facet:dg_dim*unique_facet+m, :] = non_zeros
-
-        # Return expected dictionary
-        tdim = self.cell.get_spatial_dimension()
-        key = tuple(0 for i in range(tdim))
-
-        return {key: values}
-
-    # These functions are only needed for evaluatebasis and
-    # evaluatebasisderivatives, disable those, and we should be in
-    # business.
-    def get_coeffs(self):
-        """Return the expansion coefficients for the basis of the
-        finite element."""
-        msg = "Not implemented: shouldn't be implemented."
-        raise Exception(msg)
-
-    def get_num_members(self, arg):
-        msg = "Not implemented: shouldn't be implemented."
-        raise Exception(msg)
-
-    def dmats(self):
-        msg = "Not implemented."
-        raise Exception(msg)
-
-    def __str__(self):
-        return "DiscontinuousLagrangeTrace(%s, %s)" % (self.cell, self.k)
-
-if __name__ == "__main__":
-
-    print("\n2D ----------------")
-    T = ufc_simplex(2)
-    element = DiscontinuousLagrangeTrace(T, 1)
-    pts = [(0.0, 1.0), (1.0, 0.0)]
-    print("values = ", element.tabulate(0, pts))
-
-    print("\n3D ----------------")
-    T = ufc_simplex(3)
-    element = DiscontinuousLagrangeTrace(T, 1)
-    pts = [(0.1, 0.0, 0.0), (0.0, 1.0, 0.0)]
-    print("values = ", element.tabulate(0, pts))
diff --git a/FIAT/transform_hermite.py b/FIAT/transform_hermite.py
deleted file mode 100644
index d772358..0000000
--- a/FIAT/transform_hermite.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from . import hermite, reference_element
-import numpy
-
-# Let's set up the reference triangle and another one
-Khat = reference_element.UFCTriangle()
-
-newverts = ((-1.0, 0.0), (1.0, 0.0), (0.0, 1.0))
-newtop = Khat.get_topology()
-
-K = reference_element.ReferenceElement( reference_element.TRIANGLE, \
-                                            newverts, \
-                                            newtop )
-
-# Construct the affine mapping between them
-A, b = reference_element.make_affine_mapping( K.get_vertices(),
-                                             Khat.get_vertices() )
-
-# build the Hermite element on the two triangles
-Hhat = hermite.CubicHermite( Khat )
-H = hermite.CubicHermite( K )
-
-# get some points on each triangle
-pts_hat = Khat.make_lattice( 6  )
-pts = K.make_lattice( 6 )
-
-# as a sanity check on the affine mapping, make sure
-# pts map to pts_hat
-
-for i in range( len( pts ) ):
-    if not numpy.allclose( pts_hat[i], numpy.dot(A, pts[i]) + b):
-        print("barf")
-
-# Tabulate the Hermite basis on each triangle
-Hhat_tabulated = Hhat.get_nodal_basis().tabulate_new( pts_hat )
-H_tabulated = H.get_nodal_basis().tabulate_new( pts )
-
-# transform:
-M = numpy.zeros( (10, 10), "d" )
-
-Ainv = numpy.linalg.inv( A )
-
-# entries for point values are easy
-M[0, 0] = 1.0
-M[3, 3] = 1.0
-M[6, 6] = 1.0
-M[9, 9] = 1.0
-M[1:3, 1:3] = numpy.transpose( Ainv )
-M[4:6, 4:6] = numpy.transpose( Ainv )
-M[7:9, 7:9] = numpy.transpose( Ainv )
-# entries for rest are Jacobian
-
-
-print(numpy.max( numpy.abs( H_tabulated - numpy.dot( numpy.transpose( M ), Hhat_tabulated ) ) ))
-
diff --git a/FIAT/transform_morley.py b/FIAT/transform_morley.py
deleted file mode 100644
index f2e4ab9..0000000
--- a/FIAT/transform_morley.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright (C) 2008-2012 Robert C. Kirby (Texas Tech University)
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-from . import morley, reference_element
-import numpy
-
-# Let's set up the reference triangle and another one
-Khat = reference_element.UFCTriangle()
-
-newverts = ((-1.0, 0.0), (42.0, -0.5), (0.0, 1.0))
-newtop = Khat.get_topology()
-
-K = reference_element.ReferenceElement( reference_element.TRIANGLE, \
-                                            newverts, \
-                                            newtop )
-
-# Construct the affine mapping between them
-A, b = reference_element.make_affine_mapping( K.get_vertices(),
-                                             Khat.get_vertices() )
-
-# build the Morley element on the two triangles
-Mhat = morley.Morley( Khat )
-M = morley.Morley( K )
-
-# get some points on each triangle
-pts_hat = Khat.make_lattice( 4, 1 )
-pts = K.make_lattice( 4, 1 )
-
-# as a sanity check on the affine mapping, make sure
-# pts map to pts_hat
-
-for i in range( len( pts ) ):
-    if not numpy.allclose( pts_hat[i], numpy.dot(A, pts[i]) + b):
-        print("barf")
-
-# Tabulate the Morley basis on each triangle
-Mhat_tabulated = Mhat.get_nodal_basis().tabulate_new( pts_hat )
-M_tabulated = M.get_nodal_basis().tabulate_new( pts )
-
-Ainv = numpy.linalg.inv( A )
-AinvT = numpy.transpose( Ainv )
-
-D = numpy.zeros( (6, 9), "d" )
-E = numpy.zeros( (9, 6), "d" )
-
-D[0, 0] = 1.0
-D[1, 1] = 1.0
-D[2, 2] = 1.0
-
-for i in range(3):
-    n = K.compute_normal(i)
-    t = K.compute_normalized_edge_tangent(i)
-    nhat = Khat.compute_normal(i)
-    l = K.volume_of_subcomplex(1, i)
-    nt = numpy.transpose( [ n, t ] )
-    [f, g] = numpy.dot( nhat, numpy.dot( AinvT, nt ) ) / l
-    D[3+i, 3+i] = f
-    D[3+i, 6+i] = g
-
-for d in D.tolist():
-    print(d)
-print()
-
-for i in range(3):
-    E[i, i] = 1.0
-
-for i in range(3):
-    E[3+i, 3+i] = K.volume_of_subcomplex(1, i)
-
-for i in range(3):
-    evids = K.topology[1][i]
-    elen = K.volume_of_subcomplex( 1, i )
-    E[6+i, evids[1]] = 1.0
-    E[6+i, evids[0]] = -1.0
-
-print(E)
-print()
-transform = numpy.dot( D, E )
-ttrans = numpy.transpose( transform )
-
-for row in ttrans:
-    print(row)
-print()
-
-print("max error")
-print(numpy.max( numpy.abs( numpy.dot( numpy.transpose( transform ), Mhat_tabulated )  - M_tabulated ) ))
-
-
-
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index d6d24e2..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,32 +0,0 @@
-setup.py
-FIAT/BDFM.py
-FIAT/BDM.py
-FIAT/CrouzeixRaviart.py
-FIAT/DiscontinuousLagrange.py
-FIAT/Lagrange.py
-FIAT/Nedelec.py
-FIAT/P0.py
-FIAT/PhiK.py
-FIAT/RaviartThomas.py
-FIAT/__init__.py
-FIAT/constrainedspaces.py
-FIAT/divfree.py
-FIAT/dualbasis.py
-FIAT/expansions.py
-FIAT/factorial.py
-FIAT/functional.py
-FIAT/functionalset.py
-FIAT/gamma.py
-FIAT/jacobi.py
-FIAT/newquad.py
-FIAT/numbering.py
-FIAT/polynomial.py
-FIAT/quadrature.py
-FIAT/shapes.py
-FIAT/test.py
-FIAT/testBDFM.py
-FIAT/testBDM.py
-FIAT/testRT.py
-FIAT/testfunctional.py
-FIAT/testned.py
-FIAT/xpermutations.py
diff --git a/README b/README.rst
similarity index 54%
rename from README
rename to README.rst
index c012e38..73e0ec5 100644
--- a/README
+++ b/README.rst
@@ -11,8 +11,47 @@ such as the families of Raviart-Thomas, Brezzi-Douglas-Marini and
 Nedelec are supported on triangles and tetrahedra. Upcoming versions
 will also support Hermite and nonconforming elements.
 
+FIAT is part of the FEniCS Project.
+
 For more information, visit http://www.fenicsproject.org
 
+
+Documentation
+=============
+
+Documentation can be viewed at http://fenics-fiat.readthedocs.org/.
+
+.. image:: https://readthedocs.org/projects/fenics-fiat/badge/?version=latest
+   :target: http://fenics.readthedocs.io/projects/fiat/en/latest/?badge=latest
+   :alt: Documentation Status
+
+
+Automated Testing
+-----------------
+
+We use Bitbucket Pipelines and Atlassian Bamboo to perform automated
+testing.
+
+.. image:: https://bitbucket-badges.useast.atlassian.io/badge/fenics-project/fiat.svg
+   :target: https://bitbucket.org/fenics-project/fiat/addon/pipelines/home
+   :alt: Pipelines Build Status
+
+.. image:: http://fenics-bamboo.simula.no:8085/plugins/servlet/wittified/build-status/FIAT-FD
+   :target: http://fenics-bamboo.simula.no:8085/browse/FIAT-FD/latest
+   :alt: Bamboo Build Status
+
+
+Code Coverage
+-------------
+
+Code coverage reports can be viewed at
+https://coveralls.io/repos/bitbucket/fenics-project/fiat.
+
+.. image:: https://coveralls.io/repos/bitbucket/fenics-project/fiat/badge.svg?branch=master
+   :target: https://coveralls.io/bitbucket/fenics-project/fiat?branch=master
+   :alt: Coverage Status
+
+
 License
 =======
 
@@ -28,10 +67,3 @@ License
 
   You should have received a copy of the GNU Lesser General Public License
   along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-Dependencies
-============
-
-#. Python, version 2.7 or later
-
-#. The Python modules NumPy and SymPy
diff --git a/doc/fenicsmanual.cls b/doc/fenicsmanual.cls
deleted file mode 100644
index 5b015be..0000000
--- a/doc/fenicsmanual.cls
+++ /dev/null
@@ -1,100 +0,0 @@
-% Copyright (C) 2005 Anders Logg.
-% Licensed under the GNU GPL Version 2.
-%
-% First added:  2004-09-03
-% Last changed: 2005-09-30
-%
-% LaTeX document class for FEniCS manuals.
-
-%--- Set up class ----
-\ProvidesClass{fenicsmanual}[2005/09/03 FEniCS manual]
-\NeedsTeXFormat{LaTeX2e}
-\LoadClass[12pt,twoside]{book}
-
-%--- Load packages ---
-\RequirePackage{graphicx}
-\RequirePackage{psfrag}
-\RequirePackage{fancyhdr}
-\RequirePackage{fancybox}
-\RequirePackage{fancyvrb}
-\RequirePackage{sectsty}
-\RequirePackage{amsmath}
-\RequirePackage{amssymb}
-\RequirePackage{makeidx}
-\RequirePackage{url}
-\RequirePackage[latin1]{inputenc}
-\RequirePackage[colorlinks]{hyperref}
-
-%--- Misc options ---
-\setlength{\parindent}{0pt}
-\setlength{\parskip}{12pt}
-\allsectionsfont{\sffamily}
-\makeindex
-
-%--- Remove header and footer from blank pages  ---
-\let\origdoublepage\cleardoublepage
-\newcommand{\clearemptydoublepage}{%
-  \clearpage
-  {\pagestyle{empty}\origdoublepage}%
-}
-\let\cleardoublepage\clearemptydoublepage
-
-%--- Print index at end of document ---
-\AtEndDocument{\cleardoublepage\printindex}
-
-%--- Variables ---
-\newcommand{\@fenicstitle}{}
-\newcommand{\fenicstitle}[1]{\renewcommand{\@fenicstitle}{#1}}
-\newcommand{\@fenicsauthor}{}
-\newcommand{\fenicsauthor}[1]{\renewcommand{\@fenicsauthor}{#1}}
-\newcommand{\@fenicsimage}{\vspace{8cm}}
-\newcommand{\fenicsimage}[1]{\renewcommand{\@fenicsimage}{
-    \begin{center}
-      \includegraphics[height=8cm]{#1}
-    \end{center}}}
-\newcommand{\@fenicspackage}{<package unspecified>}
-\newcommand{\@fenicspackagett}{<package unspecified>}
-\newcommand{\fenicspackage}[2]{\renewcommand{\@fenicspackage}{#1}\renewcommand{\@fenicspackagett}{#2}}
-\newcommand{\package}{\@fenicspackage}
-\newcommand{\packagett}{\@fenicspackagett}
-
-%--- Commands ---
-\renewcommand{\maketitle}{
-  \lhead{\textsf{\textbf{\@fenicstitle}}}
-  \rhead{\textsf{\@fenicsauthor}}
-  \pagestyle{fancy}
-  \renewcommand{\footrulewidth}{2pt}
-  \renewcommand{\headrulewidth}{2pt}
-  \thispagestyle{empty}
-  \Large\textsf{\textbf{\@fenicstitle}} \\
-  \vspace{-0.5cm}
-  \hrule height 2pt
-  \hfill\large\textsf{\today}
-  \vspace{3cm}
-  \@fenicsimage
-  \vfill\large\textsf{\textbf{\@fenicsauthor}} \\
-  \hrule height 2pt
-  \hfill\large\texttt{www.fenics.org}
-  \newpage
-  \null\vfill
-  \normalsize
-  Visit \texttt{http://www.fenics.org/} for the latest version of this manual. \\
-  Send comments and suggestions to \texttt{\@fenicspackagett{}-dev at fenics.org}.
-  \thispagestyle{empty}
-  \cleardoublepage
-  \tableofcontents}
-
-\newcommand{\fenics}{\textbf{\textsf{\normalsize{FE}\Large{ni}\normalsize{CS}}}}
-\newcommand{\dolfin}{\textbf{\textsf{DOLFIN}}}
-\newcommand{\ffc}{\textbf{\textsf{FFC}}}
-\newcommand{\fiat}{\textbf{\textsf{FIAT}}}
-\newcommand{\fixme}[1]{\ \\ \begin{tabular}{||p{\textwidth}||}\hline\rm\textbf{FIXME:}\rm #1 \\ \hline\end{tabular} \\}
-\newcommand{\devnote}[1]{$\blacktriangleright$ \emph{Developer's note:} #1}
-
-%--- Environments ---
-\DefineVerbatimEnvironment{code}{Verbatim}{commandchars=\\\{\},frame=single,rulecolor=\color{blue}}
-
-%--- Macros ---
-\newcommand{\dx}{\, \mathrm{d}x}
-\newcommand{\dX}{\, \mathrm{d}X}
-\newcommand{\R}{\mathbb{R}}
diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile
new file mode 100644
index 0000000..995d183
--- /dev/null
+++ b/doc/sphinx/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FIniteelementAutomaticTabulatorFIAT.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FIniteelementAutomaticTabulatorFIAT.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/FIniteelementAutomaticTabulatorFIAT"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FIniteelementAutomaticTabulatorFIAT"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/doc/sphinx/requirements.txt b/doc/sphinx/requirements.txt
new file mode 100644
index 0000000..05bdf19
--- /dev/null
+++ b/doc/sphinx/requirements.txt
@@ -0,0 +1,2 @@
+numpy
+sympy
diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py
new file mode 100644
index 0000000..1ec1d8d
--- /dev/null
+++ b/doc/sphinx/source/conf.py
@@ -0,0 +1,292 @@
+# -*- coding: utf-8 -*-
+#
+# FInite element Automatic Tabulator (FIAT) documentation build configuration file, created by
+# sphinx-quickstart on Wed Nov  4 15:38:29 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+import shlex
+
+# 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
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.coverage',
+    'sphinx.ext.mathjax',
+    'sphinx.ext.viewcode',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'FInite element Automatic Tabulator (FIAT)'
+copyright = u'2015, 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 FIAT
+fiat_version = FIAT.__version__
+
+# The short X.Y version.
+version = fiat_version
+# The full version, including alpha/beta/rc tags.
+release = fiat_version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'FIniteelementAutomaticTabulatorFIATdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  ('index', 'FIniteelementAutomaticTabulatorFIAT.tex', u'FInite element Automatic Tabulator (FIAT) Documentation',
+   u'FEniCS Project', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'finiteelementautomatictabulatorfiat', u'FInite element Automatic Tabulator (FIAT) Documentation',
+     [u'FEniCS Project'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'FIniteelementAutomaticTabulatorFIAT', u'FInite element Automatic Tabulator (FIAT) Documentation',
+   u'FEniCS Project', 'FIniteelementAutomaticTabulatorFIAT', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+
+def run_apidoc(_):
+    modules = ['FIAT']
+
+    # Get location of Sphinx files
+    sphinx_source_dir = os.path.abspath(os.path.dirname(__file__))
+    repo_dir = os.path.abspath(os.path.join(sphinx_source_dir, os.path.pardir,
+                                            os.path.pardir, os.path.pardir))
+    apidoc_dir = os.path.join(sphinx_source_dir, "api-doc")
+
+    from sphinx.apidoc import main
+    for module in modules:
+        # Generate .rst files ready for autodoc
+        module_dir = os.path.join(repo_dir, module)
+        main(["-f", "-d", "1", "-o", apidoc_dir, module_dir])
+
+def setup(app):
+    app.connect('builder-inited', run_apidoc)
diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst
new file mode 100644
index 0000000..9dc306d
--- /dev/null
+++ b/doc/sphinx/source/index.rst
@@ -0,0 +1,37 @@
+.. title:: FIAT
+
+
+========================================
+FIAT: FInite element Automatic Tabulator
+========================================
+
+FIAT is a Python package for automatic generation of finite element
+basis functions. It is capable of generating finite element basis
+functions for a wide range of finite element families on simplices
+(lines, triangles and tetrahedra), including the Lagrange elements,
+and the elements of Raviart-Thomas, Brezzi-Douglas-Marini and Nedelec.
+It is also capable of generating tensor-product elements and a number
+more exotic elements, such as the Argyris, Hermite and Morley
+elements.
+
+FIAT is part of the FEniCS Project.
+
+For more information, visit http://www.fenicsproject.org.
+
+
+Documentation
+=============
+
+.. toctree::
+   :titlesonly:
+   :maxdepth: 1
+
+   installation
+   manual
+   API reference <api-doc/FIAT>
+   releases
+
+[FIXME: These links don't belong here, should go under API reference somehow.]
+
+* :ref:`genindex`
+* :ref:`modindex`
diff --git a/doc/sphinx/source/installation.rst b/doc/sphinx/source/installation.rst
new file mode 100644
index 0000000..983d34c
--- /dev/null
+++ b/doc/sphinx/source/installation.rst
@@ -0,0 +1,49 @@
+.. title:: Installation
+
+
+============
+Installation
+============
+
+FIAT is normally installed as part of an installation of FEniCS.
+If you are using FIAT as part of the FEniCS software suite, it
+is recommended that you follow the
+`installation instructions for FEniCS
+<https://fenics.readthedocs.io/en/latest/>`__.
+
+To install FIAT itself, read on below for a list of requirements
+and installation instructions.
+
+
+Requirements and dependencies
+=============================
+
+FIAT requires Python version 2.7 or later and depends on the
+following Python packages:
+
+* NumPy
+* SymPy
+* six
+
+These packages will be automatically installed as part of the
+installation of FIAT, if not already present on your system.
+
+
+Installation instructions
+=========================
+
+To install FIAT, download the source code from the
+`FIAT Bitbucket repository
+<https://bitbucket.org/fenics-project/fiat>`__,
+and run the following command:
+
+.. code-block:: console
+
+    pip install .
+
+To install to a specific location, add the ``--prefix`` flag
+to the installation command:
+
+.. code-block:: console
+
+    pip install --prefix=<some directory> .
diff --git a/doc/manual.tex b/doc/sphinx/source/manual.rst
similarity index 85%
rename from doc/manual.tex
rename to doc/sphinx/source/manual.rst
index 9d2f864..36288f2 100644
--- a/doc/manual.tex
+++ b/doc/sphinx/source/manual.rst
@@ -1,44 +1,12 @@
-\documentclass{fenicsmanual}
-\usepackage{graphicx}
-\usepackage{amssymb}
-\usepackage{amsmath}
-\usepackage{url}
+.. title:: User manual
 
-\textwidth = 6.5in
-\textheight = 9in
-\oddsidemargin = 0in
-\evensidemargin = 0in
-\topmargin = 0.0 in
-\headheight = 0.0 in
-\headsep = 0.0 in
-\parskip = 0.2in
-\parindent = 0.0in
 
-\newtheorem{theorem}{Theorem}
-\newtheorem{lemma}[theorem]{Lemma}
-\newtheorem{corollary}[theorem]{Corollary}
-\newtheorem{definition}{Definition}
-\newtheorem{remark}{Remark}
-\newtheorem{example}{Example}
-\newcommand{\tuple}[2]{<\!#1,#2\!>}
-\newcommand{\meet}{\wedge}
-\newcommand{\bigmeet}{\bigwedge}
-\def\proof{\par{\it Proof}. \ignorespaces}
+===========
+User manual
+===========
 
-\def\endproof{\vbox{\hrule height0.6pt\hbox{%
-   \vrule height1.3ex width0.6pt\hskip0.8ex
-   \vrule width0.6pt}\hrule height0.6pt
-  }}
+.. note:: This page is work in progress and needs substantial editing.
 
-\newcommand{\N}{{\sf
-    N\hspace*{-1.0ex}\rule{0.15ex}{1.3ex}\hspace*{1.0ex}}}
-
-\title{FIAT 0.2.4 Users' Manual}
-\author{Robert C. Kirby}
-\begin{document}
-\maketitle
-
-\chapter{Introduction}
 FIAT (FInite element Automatic Tabulator) is a Python package for
 defining and evaluating a wide range of different finite element basis
 functions for numerical partial differential equations.  It is
@@ -71,18 +39,6 @@ derivatives at some quadrature points.  Then, I will explain some of
 the underlying infrastructure so as to demonstrate how to add new
 elements.
 
-\chapter{Installation}
-FIAT uses the standard Python \texttt{distutils} tools.  From the top
-directory, one executes \texttt{python setup.py install}.  This will
-put FIAT into the \texttt{site-packages} directory.  Super-user
-permission (such as \texttt{su} or \texttt{sudo}) may be required to
-write to this directory.  For more configuration options, one may type
-\texttt{python setup.py --help} or consult the online Python
-documentation at \texttt{http://docs.python.org/inst/inst.html}
-
-FIAT requires the commonly used \verb.Numeric. package.
-
-
 \chapter{Using FIAT: A tutorial with Lagrange elements}
 \section{Importing FIAT}
 FIAT is organized as a package in Python, consisting of several
@@ -126,7 +82,7 @@ abstract definition of Ciarlet.  These methods are
 The first of these returns the code for the shape and the second
 returns the nodes of the finite element (including information related
 to topological association of nodes with mesh entities, needed for
-creating degree of freedom orderings).  
+creating degree of freedom orderings).
 
 \section{Quadrature rules}
 FIAT implements arbitrary-order collapsed quadrature, as discussed in
@@ -198,7 +154,7 @@ array([[-0.83278049, -0.06003983,  0.14288254,  0.34993778],
        [ 0.31010205,  1.28989795,  0.31010205,  1.28989795],
        [-0.31010205, -1.28989795, -0.31010205, -1.28989795],
        [ 0.97566304,  0.40997761, -0.97566304, -0.40997761]])
->>> Ufs_jet[1][(0,1)] 
+>>> Ufs_jet[1][(0,1)]
 array([[ -8.32780492e-01,  -6.00398310e-02,   1.42882543e-01,   3.49937780e-01],
        [  7.39494156e-17,   4.29608279e-17,   4.38075188e-17,   7.47961065e-17],
        [ -1.89897949e-01,   7.89897949e-01,  -1.89897949e-01,   7.89897949e-01],
@@ -283,24 +239,4 @@ polynomials and finite element spaces.
 
 \subsection{\texttt{PolynomialSet}}
 The \texttt{PolynomialSet} function is a factory function interface into
-the hierarchy 
-
-
-
-
-\chapter{Wish list and open problems}
-While FIAT is highly functional as a tool for tabulating basis
-functions at quadrature points, there are a lot of interesting
-things to do.  In case anybody wants to help out, I have
-chosen to describe some of these issues here.
-
-\section{Stable/fast VDM inversion}
-
-\section{Symmetric quadrature rules}
-
-\section{Declarative top-level language}
-
-\section{Integration with SMART-type tools}
-
-
-\end{document}
+the hierarchy
diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst
new file mode 100644
index 0000000..796ed29
--- /dev/null
+++ b/doc/sphinx/source/releases.rst
@@ -0,0 +1,13 @@
+.. title:: Release notes
+
+
+=============
+Release notes
+=============
+
+.. toctree::
+   :maxdepth: 2
+
+   releases/next
+   releases/v2016.1.0
+   releases/v1.6.0
diff --git a/doc/sphinx/source/releases/next.rst b/doc/sphinx/source/releases/next.rst
new file mode 100644
index 0000000..d6610a7
--- /dev/null
+++ b/doc/sphinx/source/releases/next.rst
@@ -0,0 +1,47 @@
+===========================
+Changes in the next release
+===========================
+
+
+Summary of changes
+==================
+
+.. note:: Developers should use this page to track and list changes
+          during development. At the time of release, this page should
+          be published (and renamed) to list the most important
+          changes in the new release.
+
+- More elegant edge-based degrees of freedom are used for generalized Regge
+  finite elements.  This is a internal change and is not visible to other parts
+  of FEniCS.
+- The name of the mapping for generalized Regge finite element is changed to
+  "double covariant piola" from "pullback as metric". Geometrically, this
+  mapping is just the pullback of covariant 2-tensor fields in terms of proxy
+  matrix-fields. Because the mapping for 1-forms in FEniCS is currently named
+  "covariant piola", this mapping for symmetric tensor product of 1-forms is
+  thus called "double covariant piola". This change causes multiple internal
+  changes downstream in UFL and FFC. But this change should not be visible to
+  the end-user.
+- Added support for the Hellan-Herrmann-Johnson element (symmetric matrix
+  fields with normal-normal continuity in 2D).
+- Add method ``FiniteElement.is_nodal()`` for checking element nodality
+- Add ``NodalEnrichedElement`` which merges dual bases (nodes) of given
+  elements and orthogonalizes basis for nodality
+- Restructuring ``finite_element.py`` with the addition of a non-nodal class
+  ``FiniteElement`` and a nodal class ``CiarletElement``. ``FiniteElement`` is
+  designed to be used to create elements where, in general, a nodal basis isn't
+  well-defined. ``CiarletElement`` implements the usual nodal abstraction of
+  a finite element.
+- Removing ``trace.py`` and ``trace_hdiv.py`` with a new implementation of the
+  trace element of an HDiv-conforming element: ``HDivTrace``. It is also
+  mathematically equivalent to the former ``DiscontinuousLagrangeTrace``, that
+  is, the DG field defined only on co-dimension 1 entities.
+- All nodal finite elements inherit from ``CiarletElement``, and the non-nodal
+  ``TensorProductElement``, ``EnrichedElement`` and ``HDivTrace`` inherit from
+  ``FiniteElement``.
+
+Detailed changes
+================
+
+.. note:: At the time of release, make a verbatim copy of the
+          ChangeLog here (and remove this note).
diff --git a/doc/sphinx/source/releases/v1.6.0.rst b/doc/sphinx/source/releases/v1.6.0.rst
new file mode 100644
index 0000000..9cfabe4
--- /dev/null
+++ b/doc/sphinx/source/releases/v1.6.0.rst
@@ -0,0 +1,8 @@
+========================
+Changes in version 1.6.0
+========================
+
+FIAT 1.6.0 was released on 2015-07-28.
+
+- Support DG on facets through the element ``Discontinuous Lagrange
+  Trace``
diff --git a/doc/sphinx/source/releases/v2016.1.0.rst b/doc/sphinx/source/releases/v2016.1.0.rst
new file mode 100644
index 0000000..40797b4
--- /dev/null
+++ b/doc/sphinx/source/releases/v2016.1.0.rst
@@ -0,0 +1,7 @@
+===========================
+Changes in version 2016.1.0
+===========================
+
+FIAT 2016.1.0 was released on 2016-06-23.
+
+- Minor fixes
diff --git a/release.conf b/release.conf
deleted file mode 100644
index dd07c44..0000000
--- a/release.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-# Configuration file for fenics-release
-
-PACKAGE="fiat"
-BRANCH="master"
-FILES="setup.py ChangeLog FIAT/__init__.py"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..a97610f
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,7 @@
+[flake8]
+ignore =
+    E501,E226,E731,
+    FI14,FI54,
+    FI50,FI51,FI53
+exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist
+min-version = 2.7
diff --git a/setup.py b/setup.py
index 70a9937..ab4782e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+from __future__ import absolute_import, print_function, division
+
 import re
 import sys
 
@@ -17,7 +19,7 @@ version = re.findall('__version__ = "(.*)"',
 
 url = "https://bitbucket.org/fenics-project/fiat/"
 tarball = None
-if not 'dev' in version:
+if 'dev' not in version:
     tarball = url + "downloads/fiat-%s.tar.gz" % version
 
 setup(name="FIAT",
@@ -29,4 +31,4 @@ setup(name="FIAT",
       download_url=tarball,
       license="LGPL v3 or later",
       packages=["FIAT"],
-      install_requires=["numpy", "sympy"])
+      install_requires=["numpy", "sympy", "six"])
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..6a6f779
--- /dev/null
+++ b/test/README
@@ -0,0 +1,6 @@
+Run tests by::
+
+    py.test [--skip-download]
+    py.test [--skip-download] regression/
+    py.test unit/
+    py.test unit/foo.py
diff --git a/FIAT/factorial.py b/test/conftest.py
similarity index 65%
rename from FIAT/factorial.py
rename to test/conftest.py
index 638e83f..1477e56 100644
--- a/FIAT/factorial.py
+++ b/test/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 Robert C. Kirby (Texas Tech University)
+# Copyright (C) 2016 Jan Blechta
 #
 # This file is part of FIAT.
 #
@@ -15,13 +15,9 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with FIAT. If not, see <http://www.gnu.org/licenses/>.
 
-def factorial( n ):
-    """Computes n! for n an integer >= 0.
-    Raises an ArithmeticError otherwise."""
-    if not isinstance(n, type(1)) or n < 0:
-        raise ArithmeticError("factorial only defined on natural numbers.")
-    f = 1
-    for i in range(1, n+1):
-        f = f * i
-    return f
+from __future__ import absolute_import, print_function, division
 
+
+def pytest_addoption(parser):
+    parser.addoption("--skip-download", dest='download', action='store_false',
+                     help="do not download FIAT reference data")
diff --git a/test/regression/.gitignore b/test/regression/.gitignore
deleted file mode 100644
index 56d0de7..0000000
--- a/test/regression/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-fiat-reference-data/
diff --git a/test/regression/README.rst b/test/regression/README.rst
index db9f519..0a145ee 100644
--- a/test/regression/README.rst
+++ b/test/regression/README.rst
@@ -1,10 +1,10 @@
 How to run regression tests
 ===========================
 
-To run regression tests with default parameters, simply run:
+To run regression tests with default parameters, simply run::
 
   cd <fiatdir>/test/regression/
-  python test.py
+  py.test
 
 Look at test.py for more options.
 
@@ -14,11 +14,11 @@ How to update references
 
 To update the references for the FIAT regression tests, first commit
 your changes, then run the regression test (to generate the new
-references) and finally run the script upload:
+references) and finally run the script upload::
 
   <commit your changes>
   cd <fiatdir>/test/regression/
-  python test.py
+  py.test
   ./scripts/upload
 
 Note: You may be asked for your *Bitbucket* username and password when
@@ -47,12 +47,16 @@ How to run regression tests against a different set of regression data
 To run regression tests and compare to a different set of regression
 data, perhaps to see what has changed in generated code since a
 certain version, check out the fiat-regression-data-id file you want
-and run tests as usual
+and run tests as usual::
 
   cd <fiatdir>/test/regression/
   git checkout <fiat-commit-id> fiat-regression-data-id
-  python test.py
+  py.test
 
 The test.py script will run scripts/download which will check out the
 regression data with the commit id from fiat-regression-data-id in
-fiat-regression-data/.
+fiat-regression-data/. Run::
+
+    DATA_REPO_GIT="" ./scripts/download/
+
+to use https instead of ssh.
diff --git a/test/regression/conftest.py b/test/regression/conftest.py
new file mode 100644
index 0000000..72eaa4a
--- /dev/null
+++ b/test/regression/conftest.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2016 Jan Blechta
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import os
+
+
+# Directories
+path = os.path.dirname(os.path.abspath(__file__))
+ref_path = os.path.join(path, 'fiat-reference-data')
+download_script = os.path.join(path, 'scripts', 'download')
+
+
+def pytest_configure(config):
+    # Download reference data
+    if config.getoption("download"):
+        failure = download_reference()
+        if failure:
+            raise RuntimeError("Download reference data failed")
+        print("Download reference data ok")
+    else:
+        print("Skipping reference data download")
+        if not os.path.exists(ref_path):
+            os.makedirs(ref_path)
+
+
+def download_reference():
+    _path = os.getcwd()
+    os.chdir(path)
+    rc = os.system(download_script)
+    os.chdir(_path)
+    return rc
diff --git a/test/regression/fiat-reference-data-id b/test/regression/fiat-reference-data-id
index b7cbaae..103a3b9 100644
--- a/test/regression/fiat-reference-data-id
+++ b/test/regression/fiat-reference-data-id
@@ -1 +1 @@
-8ceb724ba33419e412aa30ae8d23c2f8c7ab3b84
+83d6c1d8f30d2c116398f496a4592ef541ea2843
diff --git a/test/regression/scripts/getreferencerepo b/test/regression/scripts/getreferencerepo
index ae971c7..d6d2254 100755
--- a/test/regression/scripts/getreferencerepo
+++ b/test/regression/scripts/getreferencerepo
@@ -30,7 +30,9 @@ source ./scripts/parameters
 # Get reference repository
 if [ ! -d "$DATA_DIR" ]; then
     echo "Cloning reference data repository"
-    git clone $DATA_REPO_GIT
+    if [ -n "$DATA_REPO_GIT" ]; then
+        git clone $DATA_REPO_GIT
+    fi
     if [ ! -d "$DATA_DIR" ]; then
         git clone $DATA_REPO_HTTPS
     fi
diff --git a/test/regression/scripts/parameters b/test/regression/scripts/parameters
index dbd307d..b8b0ce8 100755
--- a/test/regression/scripts/parameters
+++ b/test/regression/scripts/parameters
@@ -1,5 +1,5 @@
 #OUTPUT_DIR="output"
-DATA_REPO_GIT="git at bitbucket.org:fenics-project/fiat-reference-data.git"
+[ ! -z ${DATA_REPO_GIT+x} ] || DATA_REPO_GIT="git at bitbucket.org:fenics-project/fiat-reference-data.git"
 DATA_REPO_HTTPS="https://bitbucket.org/fenics-project/fiat-reference-data.git"
 DATA_DIR="fiat-reference-data"
 DATA_ID_FILE="fiat-reference-data-id"
diff --git a/test/regression/test.py b/test/regression/test_regression.py
similarity index 62%
rename from test/regression/test.py
rename to test/regression/test_regression.py
index 26b3c85..3b1738f 100644
--- a/test/regression/test.py
+++ b/test/regression/test_regression.py
@@ -18,20 +18,28 @@
 # First added:  2010-01-31
 # Last changed: 2014-06-30
 
-from __future__ import print_function
-import nose, json, numpy, warnings, os, sys
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import json
+import numpy
+import warnings
+import os
 
 from FIAT import supported_elements, make_quadrature, ufc_simplex, \
-    newdubiner, expansions, reference_element, polynomial_set
+    expansions, reference_element, polynomial_set
 
 # Parameters
 tolerance = 1e-8
 
-# Directory with reference data
-prefix = 'fiat-reference-data'
+# Directories
+path = os.path.dirname(os.path.abspath(__file__))
+ref_path = os.path.join(path, 'fiat-reference-data')
+download_script = os.path.join(path, 'scripts', 'download')
 
 
 class NumpyEncoder(json.JSONEncoder):
+
     def default(self, obj):
         # If numpy array, convert it to a list and store it in a dict.
         if isinstance(obj, numpy.ndarray):
@@ -50,57 +58,62 @@ def json_numpy_obj_hook(dct):
     return dct
 
 
-def test_polynomials():
-    def create_data():
-        ps = polynomial_set.ONPolynomialSet(
-            ref_el=reference_element.DefaultTetrahedron(),
-            degree=3
-            )
-        return ps.dmats
-
-    # Try reading reference values
-    filename = os.path.join(prefix, "reference-polynomials.json")
+def load_reference(filename, create_data):
+    """Load reference from file. On failure create new file using supplied
+    function.
+    """
     try:
+        # Try loading the reference
         reference = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
     except IOError:
         warnings.warn('Reference file "%s" could not be loaded! '
                       'Creating a new reference file!' % filename,
                       RuntimeWarning)
+
+        # Generate data and store for the future
         reference = create_data()
-        # Store the data for the future
         json.dump(reference, open(filename, "w"), cls=NumpyEncoder)
 
+        # Report failure
+        pytest.fail('Comparison to "%s" failed!' % filename)
+
+    return reference
+
+
+def test_polynomials():
+    def create_data():
+        ps = polynomial_set.ONPolynomialSet(
+            ref_el=reference_element.DefaultTetrahedron(),
+            degree=3
+        )
+        return ps.dmats
+
+    # Try reading reference values
+    filename = os.path.join(ref_path, "reference-polynomials.json")
+    reference = load_reference(filename, create_data)
+
     dmats = create_data()
 
     for dmat, reference_dmat in zip(dmats, reference):
         assert (abs(dmat - reference_dmat) < tolerance).all()
-    return
+
 
 def test_polynomials_1D():
     def create_data():
         ps = polynomial_set.ONPolynomialSet(
             ref_el=reference_element.DefaultLine(),
             degree=3
-            )
+        )
         return ps.dmats
 
     # Try reading reference values
-    filename = os.path.join(prefix, "reference-polynomials_1D.json")
-    try:
-        reference = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
-    except IOError:
-        warnings.warn('Reference file "%s" could not be loaded! '
-                      'Creating a new reference file!' % filename,
-                      RuntimeWarning)
-        reference = create_data()
-        # Store the data for the future
-        json.dump(reference, open(filename, "w"), cls=NumpyEncoder)
+    filename = os.path.join(ref_path, "reference-polynomials_1D.json")
+    reference = load_reference(filename, create_data)
 
     dmats = create_data()
 
     for dmat, reference_dmat in zip(dmats, reference):
         assert (abs(dmat - reference_dmat) < tolerance).all()
-    return
 
 
 def test_expansions():
@@ -114,16 +127,8 @@ def test_expansions():
         return phis, dphis
 
     # Try reading reference values
-    filename = os.path.join(prefix, "reference-expansions.json")
-    try:
-        reference = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
-    except IOError:
-        warnings.warn('Reference file "%s" could not be loaded! '
-                      'Creating a new reference file!' % filename,
-                       RuntimeWarning)
-        reference = create_data()
-        # Convert reference to list of int
-        json.dump(reference, open(filename, "w"), cls=NumpyEncoder)
+    filename = os.path.join(ref_path, "reference-expansions.json")
+    reference = load_reference(filename, create_data)
 
     table_phi, table_dphi = create_data()
     reference_table_phi, reference_table_dphi = reference
@@ -141,7 +146,6 @@ def test_expansions():
             assert abs(value - reference_value) < tolerance
             diff = numpy.array(gradient) - numpy.array(reference_gradient)
             assert (abs(diff) < tolerance).all()
-    return
 
 
 def test_expansions_jet():
@@ -154,86 +158,15 @@ def test_expansions_jet():
         F = expansions.TetrahedronExpansionSet(E)
         return F.tabulate_jet(n, pts, order)
 
-    filename = os.path.join(prefix, "reference-expansions-jet.json")
-    try:
-        reference_jet = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
-    except IOError:
-        warnings.warn('Reference file "%s" could not be loaded! '
-                      'Creating a new reference file!' % filename,
-                       RuntimeWarning)
-        reference_jet = create_data()
-        # Store the data for the future
-        json.dump(reference_jet, open(filename, "w"), cls=NumpyEncoder)
+    filename = os.path.join(ref_path, "reference-expansions-jet.json")
+    reference = load_reference(filename, create_data)
 
     # Test jet data
     data = create_data()
-    reference_data = reference_jet
-    for datum, reference_datum in zip(data, reference_data):
+    for datum, reference_datum in zip(data, reference):
         diff = numpy.array(datum) - numpy.array(reference_datum)
         assert (abs(diff) < tolerance).all()
 
-    return
-
-
-def test_newdubiner():
-    def create_data():
-        latticeK = 2
-        D = 3
-        pts = newdubiner.make_tetrahedron_lattice(latticeK, float)
-        return newdubiner.tabulate_tetrahedron_derivatives(D, pts, float)
-
-    # Try reading reference values
-    filename = os.path.join(prefix, "reference-newdubiner.json")
-    try:
-        reference = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
-    except IOError:
-        warnings.warn('Reference file "%s" could not be loaded! '
-                      'Creating a new reference file!' % filename,
-                       RuntimeWarning)
-        reference = create_data()
-        # Convert reference to list of int
-        json.dump(reference, open(filename, "w"), cls=NumpyEncoder)
-
-    # Actually perform the test
-    table = create_data()
-
-    for data, reference_data in zip(table, reference):
-        for point, reference_point in zip(data, reference_data):
-            for k in range(2):
-                diff = numpy.array(point[k]) - numpy.array(reference_point[k])
-                assert (abs(diff) < tolerance).all()
-    return
-
-
-def test_newdubiner_jet():
-    def create_data():
-        latticeK = 2
-        D = 3
-        n = 1
-        order = 2
-        pts = newdubiner.make_tetrahedron_lattice(latticeK, float)
-        return newdubiner.tabulate_jet(D, n, pts, order, float)
-
-    filename = os.path.join(prefix, "reference-newdubiner-jet.json")
-    try:
-        reference_jet = json.load(open(filename, "r"), object_hook=json_numpy_obj_hook)
-    except IOError:
-        warnings.warn('Reference file "%s" could not be loaded! '
-                      'Creating a new reference file!' % filename,
-                       RuntimeWarning)
-        reference_jet = create_data()
-        # Store the data for the future
-        json.dump(reference_jet, open(filename, "w"), cls=NumpyEncoder)
-
-    table_jet = create_data()
-    for datum, reference_datum in zip(table_jet, reference_jet):
-        for entry, reference_entry in zip(datum, reference_datum):
-            for k in range(3):
-                diff = numpy.array(entry[k]) - numpy.array(reference_entry[k])
-                assert (abs(diff) < tolerance).all()
-
-    return
-
 
 def test_quadrature():
     num_points = 3
@@ -258,6 +191,9 @@ def test_quadrature():
         ("Discontinuous Lagrange", 3, 0),
         ("Discontinuous Lagrange", 3, 1),
         ("Discontinuous Lagrange", 3, 2),
+        ("Discontinuous Taylor", 1, 0),
+        ("Discontinuous Taylor", 1, 1),
+        ("Discontinuous Taylor", 1, 2),
         ("Brezzi-Douglas-Marini", 2, 1),
         ("Brezzi-Douglas-Marini", 2, 2),
         ("Brezzi-Douglas-Marini", 2, 3),
@@ -297,8 +233,17 @@ def test_quadrature():
         ("Regge", 2, 2),
         ("Regge", 3, 0),
         ("Regge", 3, 1),
-        ("Regge", 3, 2)
-        )
+        ("Regge", 3, 2),
+        ("Bubble", 2, 3),
+        ("Bubble", 2, 4),
+        ("Bubble", 2, 5),
+        ("Bubble", 3, 4),
+        ("Bubble", 3, 5),
+        ("Bubble", 3, 6),
+        ("Hellan-Herrmann-Johnson", 2, 0),
+        ("Hellan-Herrmann-Johnson", 2, 1),
+        ("Hellan-Herrmann-Johnson", 2, 2),
+    )
 
     def create_data(family, dim, degree):
         '''Create the reference data.
@@ -324,10 +269,10 @@ def test_quadrature():
             assert eval(dtuple) in table
             assert table[eval(dtuple)].shape == reference_table[dtuple].shape
             diff = table[eval(dtuple)] - reference_table[dtuple]
-            assert (abs(diff) < tolerance).all()
-        return
+            assert (abs(diff) < tolerance).all(), \
+                "quadrature case %s %s %s failed!" % (family, dim, degree)
 
-    filename = os.path.join(prefix, "reference.json")
+    filename = os.path.join(ref_path, "reference.json")
 
     # Try comparing against references
     try:
@@ -355,42 +300,9 @@ def test_quadrature():
         # Store the data for the future
         json.dump(reference, open(filename, "w"), cls=NumpyEncoder)
 
+        # Report failure
+        pytest.fail('Comparison to "%s" failed!' % filename)
 
 
-def main(args):
-    # Download reference data
-    skip_download = "--skip-download" in args
-    if skip_download:
-        print("Skipping reference data download")
-        args.remove("--skip-download")
-        if not os.path.exists(prefix):
-            os.makedirs(prefix)
-    else:
-        failure = os.system("./scripts/download")
-        if failure:
-            print("Download reference data failed")
-            return 1
-        else:
-            print("Download reference data ok")
-
-    # Run the test
-    with warnings.catch_warnings(record=True) as warns:
-        result = nose.run()
-
-    # Handle failed test
-    if not result:
-        return 1
-
-    # Handle missing references
-    for w in warns:
-        warnings.showwarning(w.message, w.category, w.filename,
-                             w.lineno, w.line)
-    if len(warns) > 0:
-        print("References missing. New references stored into '%s'" % prefix)
-        return 1
-
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main(sys.argv))
+if __name__ == '__main__':
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/test.py b/test/test.py
deleted file mode 100644
index e658e52..0000000
--- a/test/test.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Run all tests, including unit tests and regression tests"""
-
-# Copyright (C) 2007 Anders Logg
-#
-# This file is part of fiat.
-#
-# fiat is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# fiat is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with fiat. If not, see <http://www.gnu.org/licenses/>.
-#
-# First added:  2007-06-09
-# Last changed: 2014-05-15
-
-import os, sys
-
-# Name of log file
-pwd = os.path.dirname(os.path.abspath(__file__))
-logfile = os.path.join(pwd, "test.log")
-os.system("rm -f %s" % logfile)
-
-# Tests to run
-tests = ["unit", "regression"]
-
-# Run tests
-failed = []
-for test in tests:
-    print("Running tests: %s" % test)
-    print("----------------------------------------------------------------------")
-    os.chdir(os.path.join(pwd, test))
-    #failure = os.system("python test.py | tee -a %s" % logfile)
-    failure = os.system("python test.py")
-    if failure:
-        print("Test FAILED")
-        failed.append(test)
-    print("")
-
-#print("To view the test log, use the following command: less -R test.log")
-
-sys.exit(len(failed))
diff --git a/test/unit/test.py b/test/unit/test.py
deleted file mode 100644
index 7b962d7..0000000
--- a/test/unit/test.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright (C) 2015 Jan Blechta
-#
-# This file is part of FIAT.
-#
-# FIAT is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# FIAT is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
-
-import nose
-import numpy
-
-
-def test_basis_derivatives_scaling():
-    import random
-    from FIAT.reference_element import LINE, ReferenceElement
-    from FIAT.lagrange import Lagrange
-
-    class Interval(ReferenceElement):
-        def __init__(self, a, b):
-            verts = ( (a,), (b,) )
-            edges = { 0 : ( 0, 1 ) }
-            topology = { 0 : { 0 : (0,) , 1: (1,) } , \
-                         1 : edges }
-            ReferenceElement.__init__( self, LINE, verts, topology )
-
-    random.seed(42)
-    for i in range(26):
-        a = 1000.0*(random.random() - 0.5)
-        b = 1000.0*(random.random() - 0.5)
-        a, b = min(a, b), max(a, b)
-
-        interval = Interval(a, b)
-        element = Lagrange(interval, 1)
-
-        points = [(a,), (0.5*(a+b),), (b,)]
-        tab = element.get_nodal_basis().tabulate(points, 2)
-
-        # first basis function
-        nose.tools.assert_almost_equal(tab[(0,)][0][0], 1.0)
-        nose.tools.assert_almost_equal(tab[(0,)][0][1], 0.5)
-        nose.tools.assert_almost_equal(tab[(0,)][0][2], 0.0)
-        # second basis function
-        nose.tools.assert_almost_equal(tab[(0,)][1][0], 0.0)
-        nose.tools.assert_almost_equal(tab[(0,)][1][1], 0.5)
-        nose.tools.assert_almost_equal(tab[(0,)][1][2], 1.0)
-
-        # first and second derivatives
-        D = 1.0 / (b - a)
-        for p in range(len(points)):
-            nose.tools.assert_almost_equal(tab[(1,)][0][p], -D)
-            nose.tools.assert_almost_equal(tab[(1,)][1][p], +D)
-            nose.tools.assert_almost_equal(tab[(2,)][0][p], 0.0)
-            nose.tools.assert_almost_equal(tab[(2,)][1][p], 0.0)
-
-
-if __name__ == "__main__":
-    nose.main()
diff --git a/test/unit/test_discontinuous_taylor.py b/test/unit/test_discontinuous_taylor.py
new file mode 100644
index 0000000..c772b82
--- /dev/null
+++ b/test/unit/test_discontinuous_taylor.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2016 Imperial College London and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#
+# David Ham
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import numpy as np
+
+
+ at pytest.mark.parametrize("dim, degree", [(dim, degree)
+                                         for dim in range(1, 4)
+                                         for degree in range(4)])
+def test_basis_values(dim, degree):
+    """Ensure that integrating a simple monomial produces the expected results."""
+    from FIAT import ufc_simplex, DiscontinuousTaylor, make_quadrature
+
+    s = ufc_simplex(dim)
+    q = make_quadrature(s, degree + 1)
+
+    fe = DiscontinuousTaylor(s, degree)
+    tab = fe.tabulate(0, q.pts)[(0,) * dim]
+
+    for test_degree in range(degree + 1):
+        coefs = [n(lambda x: x[0]**test_degree) for n in fe.dual.nodes]
+        integral = np.float(np.dot(coefs, np.dot(tab, q.wts)))
+        reference = np.dot([x[0]**test_degree
+                            for x in q.pts], q.wts)
+        assert np.isclose(integral, reference, rtol=1e-14)
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_facet_support_dofs.py b/test/unit/test_facet_support_dofs.py
new file mode 100644
index 0000000..1fc672a
--- /dev/null
+++ b/test/unit/test_facet_support_dofs.py
@@ -0,0 +1,164 @@
+# Copyright (C) 2016 Miklos Homolya
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+from six.moves import range
+
+import FIAT
+from FIAT.reference_element import UFCInterval, UFCTriangle
+from FIAT.finite_element import entity_support_dofs
+
+
+ at pytest.mark.parametrize(('base', 'extr', 'horiz_expected', 'vert_expected'),
+                         [(("Discontinuous Lagrange", 0), ("Discontinuous Lagrange", 0),
+                           {0: [0], 1: [0]},
+                           {0: [0], 1: [0]}),
+                          (("Discontinuous Lagrange", 1), ("Discontinuous Lagrange", 1),
+                           {0: [0, 2], 1: [1, 3]},
+                           {0: [0, 1], 1: [2, 3]}),
+                          (("Lagrange", 1), ("Lagrange", 1),
+                           {0: [0, 2], 1: [1, 3]},
+                           {0: [0, 1], 1: [2, 3]}),
+                          (("Discontinuous Lagrange", 0), ("Lagrange", 1),
+                           {0: [0], 1: [1]},
+                           {0: [0, 1], 1: [0, 1]}),
+                          (("Lagrange", 1), ("Discontinuous Lagrange", 0),
+                           {0: [0, 1], 1: [0, 1]},
+                           {0: [0], 1: [1]})])
+def test_quad(base, extr, horiz_expected, vert_expected):
+    elem_A = FIAT.supported_elements[base[0]](UFCInterval(), base[1])
+    elem_B = FIAT.supported_elements[extr[0]](UFCInterval(), extr[1])
+    elem = FIAT.TensorProductElement(elem_A, elem_B)
+    assert horiz_expected == entity_support_dofs(elem, (1, 0))
+    assert vert_expected == entity_support_dofs(elem, (0, 1))
+
+
+def test_quad_rtce():
+    W0_h = FIAT.Lagrange(UFCInterval(), 1)
+    W1_h = FIAT.DiscontinuousLagrange(UFCInterval(), 0)
+
+    W0_v = FIAT.DiscontinuousLagrange(UFCInterval(), 0)
+    W0 = FIAT.Hcurl(FIAT.TensorProductElement(W0_h, W0_v))
+
+    W1_v = FIAT.Lagrange(UFCInterval(), 1)
+    W1 = FIAT.Hcurl(FIAT.TensorProductElement(W1_h, W1_v))
+
+    elem = FIAT.EnrichedElement(W0, W1)
+    assert {0: [0, 1, 2], 1: [0, 1, 3]} == entity_support_dofs(elem, (1, 0))
+    assert {0: [0, 2, 3], 1: [1, 2, 3]} == entity_support_dofs(elem, (0, 1))
+
+
+def test_quad_rtcf():
+    W0_h = FIAT.Lagrange(UFCInterval(), 1)
+    W1_h = FIAT.DiscontinuousLagrange(UFCInterval(), 0)
+
+    W0_v = FIAT.DiscontinuousLagrange(UFCInterval(), 0)
+    W0 = FIAT.Hdiv(FIAT.TensorProductElement(W0_h, W0_v))
+
+    W1_v = FIAT.Lagrange(UFCInterval(), 1)
+    W1 = FIAT.Hdiv(FIAT.TensorProductElement(W1_h, W1_v))
+
+    elem = FIAT.EnrichedElement(W0, W1)
+    assert {0: [0, 1, 2], 1: [0, 1, 3]} == entity_support_dofs(elem, (1, 0))
+    assert {0: [0, 2, 3], 1: [1, 2, 3]} == entity_support_dofs(elem, (0, 1))
+
+
+ at pytest.mark.parametrize(('base', 'extr', 'horiz_expected', 'vert_expected'),
+                         [(("Discontinuous Lagrange", 0), ("Discontinuous Lagrange", 0),
+                           {0: [0], 1: [0]},
+                           {0: [0], 1: [0], 2: [0]}),
+                          (("Discontinuous Lagrange", 1), ("Discontinuous Lagrange", 1),
+                           {0: [0, 2, 4], 1: [1, 3, 5]},
+                           {0: [2, 3, 4, 5], 1: [0, 1, 4, 5], 2: [0, 1, 2, 3]}),
+                          (("Lagrange", 1), ("Lagrange", 1),
+                           {0: [0, 2, 4], 1: [1, 3, 5]},
+                           {0: [2, 3, 4, 5], 1: [0, 1, 4, 5], 2: [0, 1, 2, 3]}),
+                          (("Discontinuous Lagrange", 0), ("Lagrange", 1),
+                           {0: [0], 1: [1]},
+                           {0: [0, 1], 1: [0, 1], 2: [0, 1]}),
+                          (("Lagrange", 1), ("Discontinuous Lagrange", 0),
+                           {0: [0, 1, 2], 1: [0, 1, 2]},
+                           {0: [1, 2], 1: [0, 2], 2: [0, 1]})])
+def test_prism(base, extr, horiz_expected, vert_expected):
+    elem_A = FIAT.supported_elements[base[0]](UFCTriangle(), base[1])
+    elem_B = FIAT.supported_elements[extr[0]](UFCInterval(), extr[1])
+    elem = FIAT.TensorProductElement(elem_A, elem_B)
+    assert horiz_expected == entity_support_dofs(elem, (2, 0))
+    assert vert_expected == entity_support_dofs(elem, (1, 1))
+
+
+ at pytest.mark.parametrize(('space', 'degree', 'horiz_expected', 'vert_expected'),
+                         [("Raviart-Thomas", 1,
+                           {0: [0, 1, 2, 3], 1: [0, 1, 2, 4]},
+                           {0: list(range(5)), 1: list(range(5)), 2: list(range(5))}),
+                          ("Brezzi-Douglas-Marini", 1,
+                           {0: [0, 1, 2, 3, 4, 5, 6], 1: [0, 1, 2, 3, 4, 5, 7]},
+                           {0: list(range(8)), 1: list(range(8)), 2: list(range(8))})])
+def test_prism_hdiv(space, degree, horiz_expected, vert_expected):
+    W0_h = FIAT.supported_elements[space](UFCTriangle(), degree)
+    W1_h = FIAT.DiscontinuousLagrange(UFCTriangle(), degree - 1)
+
+    W0_v = FIAT.DiscontinuousLagrange(UFCInterval(), degree - 1)
+    W0 = FIAT.Hdiv(FIAT.TensorProductElement(W0_h, W0_v))
+
+    W1_v = FIAT.Lagrange(UFCInterval(), degree)
+    W1 = FIAT.Hdiv(FIAT.TensorProductElement(W1_h, W1_v))
+
+    elem = FIAT.EnrichedElement(W0, W1)
+    assert horiz_expected == entity_support_dofs(elem, (2, 0))
+    assert vert_expected == entity_support_dofs(elem, (1, 1))
+
+
+ at pytest.mark.parametrize(('space', 'degree', 'horiz_expected', 'vert_expected'),
+                         [("Raviart-Thomas", 1,
+                           {0: [0, 1, 2, 3, 5, 7], 1: [0, 1, 2, 4, 6, 8]},
+                           {0: [1, 2] + list(range(3, 9)),
+                            1: [0, 2] + list(range(3, 9)),
+                            2: [0, 1] + list(range(3, 9))}),
+                          ("Brezzi-Douglas-Marini", 1,
+                           {0: list(range(3)) + list(range(3, 15, 2)),
+                            1: list(range(3)) + list(range(4, 15, 2))},
+                           {0: [1, 2] + list(range(3, 15)),
+                            1: [0, 2] + list(range(3, 15)),
+                            2: [0, 1] + list(range(3, 15))})])
+def test_prism_hcurl(space, degree, horiz_expected, vert_expected):
+    W0_h = FIAT.Lagrange(UFCTriangle(), degree)
+    W1_h = FIAT.supported_elements[space](UFCTriangle(), degree)
+
+    W0_v = FIAT.DiscontinuousLagrange(UFCInterval(), degree - 1)
+    W0 = FIAT.Hcurl(FIAT.TensorProductElement(W0_h, W0_v))
+
+    W1_v = FIAT.Lagrange(UFCInterval(), degree)
+    W1 = FIAT.Hcurl(FIAT.TensorProductElement(W1_h, W1_v))
+
+    elem = FIAT.EnrichedElement(W0, W1)
+    assert horiz_expected == entity_support_dofs(elem, (2, 0))
+    assert vert_expected == entity_support_dofs(elem, (1, 1))
+
+
+def test_discontinuous_element():
+    elem = FIAT.DiscontinuousElement(FIAT.Lagrange(UFCTriangle(), 3))
+    assert entity_support_dofs(elem, 1) == {0: [1, 2, 3, 4],
+                                            1: [0, 2, 5, 6],
+                                            2: [0, 1, 7, 8]}
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py
new file mode 100644
index 0000000..c81add1
--- /dev/null
+++ b/test/unit/test_fiat.py
@@ -0,0 +1,308 @@
+# Copyright (C) 2015-2016 Jan Blechta
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import random
+import numpy as np
+import pytest
+
+from FIAT.reference_element import LINE, ReferenceElement
+from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron
+from FIAT.lagrange import Lagrange
+from FIAT.discontinuous_lagrange import DiscontinuousLagrange   # noqa: F401
+from FIAT.discontinuous_taylor import DiscontinuousTaylor       # noqa: F401
+from FIAT.P0 import P0                                          # noqa: F401
+from FIAT.crouzeix_raviart import CrouzeixRaviart               # noqa: F401
+from FIAT.raviart_thomas import RaviartThomas                   # noqa: F401
+from FIAT.discontinuous_raviart_thomas import DiscontinuousRaviartThomas  # noqa: F401
+from FIAT.brezzi_douglas_marini import BrezziDouglasMarini      # noqa: F401
+from FIAT.nedelec import Nedelec                                # noqa: F401
+from FIAT.nedelec_second_kind import NedelecSecondKind          # noqa: F401
+from FIAT.regge import Regge                                    # noqa: F401
+from FIAT.hdiv_trace import HDivTrace                           # noqa: F401
+from FIAT.hellan_herrmann_johnson import HellanHerrmannJohnson  # noqa: F401
+from FIAT.brezzi_douglas_fortin_marini import BrezziDouglasFortinMarini  # noqa: F401
+from FIAT.gauss_legendre import GaussLegendre                   # noqa: F401
+from FIAT.gauss_lobatto_legendre import GaussLobattoLegendre    # noqa: F401
+from FIAT.restricted import RestrictedElement                   # noqa: F401
+from FIAT.tensor_product import TensorProductElement            # noqa: F401
+from FIAT.hdivcurl import Hdiv, Hcurl                           # noqa: F401
+from FIAT.argyris import Argyris, QuinticArgyris                # noqa: F401
+from FIAT.hermite import CubicHermite                           # noqa: F401
+from FIAT.morley import Morley                                  # noqa: F401
+from FIAT.bubble import Bubble
+from FIAT.enriched import EnrichedElement                       # noqa: F401
+from FIAT.nodal_enriched import NodalEnrichedElement
+
+
+I = UFCInterval()
+T = UFCTriangle()
+S = UFCTetrahedron()
+
+
+def test_basis_derivatives_scaling():
+    "Regression test for issue #9"
+    class Interval(ReferenceElement):
+
+        def __init__(self, a, b):
+            verts = ((a,), (b,))
+            edges = {0: (0, 1)}
+            topology = {0: {0: (0,), 1: (1,)},
+                        1: edges}
+            super(Interval, self).__init__(LINE, verts, topology)
+
+    random.seed(42)
+    for i in range(26):
+        a = 1000.0*(random.random() - 0.5)
+        b = 1000.0*(random.random() - 0.5)
+        a, b = min(a, b), max(a, b)
+
+        interval = Interval(a, b)
+        element = Lagrange(interval, 1)
+
+        points = [(a,), (0.5*(a+b),), (b,)]
+        tab = element.get_nodal_basis().tabulate(points, 2)
+
+        # first basis function
+        assert np.isclose(tab[(0,)][0][0], 1.0)
+        assert np.isclose(tab[(0,)][0][1], 0.5)
+        assert np.isclose(tab[(0,)][0][2], 0.0)
+        # second basis function
+        assert np.isclose(tab[(0,)][1][0], 0.0)
+        assert np.isclose(tab[(0,)][1][1], 0.5)
+        assert np.isclose(tab[(0,)][1][2], 1.0)
+
+        # first and second derivatives
+        D = 1.0 / (b - a)
+        for p in range(len(points)):
+            assert np.isclose(tab[(1,)][0][p], -D)
+            assert np.isclose(tab[(1,)][1][p], +D)
+            assert np.isclose(tab[(2,)][0][p], 0.0)
+            assert np.isclose(tab[(2,)][1][p], 0.0)
+
+
+xfail_impl = pytest.mark.xfail(strict=True, raises=NotImplementedError)
+xfail_key = pytest.mark.xfail(strict=True, raises=KeyError)
+elements = [
+    "Lagrange(I, 1)",
+    "Lagrange(I, 2)",
+    "Lagrange(I, 3)",
+    "Lagrange(T, 1)",
+    "Lagrange(T, 2)",
+    "Lagrange(T, 3)",
+    "Lagrange(S, 1)",
+    "Lagrange(S, 2)",
+    "Lagrange(S, 3)",
+    "P0(I)",
+    "P0(T)",
+    "P0(S)",
+    "DiscontinuousLagrange(I, 0)",
+    "DiscontinuousLagrange(I, 1)",
+    "DiscontinuousLagrange(I, 2)",
+    "DiscontinuousLagrange(T, 0)",
+    "DiscontinuousLagrange(T, 1)",
+    "DiscontinuousLagrange(T, 2)",
+    "DiscontinuousLagrange(S, 0)",
+    "DiscontinuousLagrange(S, 1)",
+    "DiscontinuousLagrange(S, 2)",
+    "DiscontinuousTaylor(I, 0)",
+    "DiscontinuousTaylor(I, 1)",
+    "DiscontinuousTaylor(I, 2)",
+    "DiscontinuousTaylor(T, 0)",
+    "DiscontinuousTaylor(T, 1)",
+    "DiscontinuousTaylor(T, 2)",
+    "DiscontinuousTaylor(S, 0)",
+    "DiscontinuousTaylor(S, 1)",
+    "DiscontinuousTaylor(S, 2)",
+    "CrouzeixRaviart(I, 1)",
+    "CrouzeixRaviart(T, 1)",
+    "CrouzeixRaviart(S, 1)",
+    "RaviartThomas(T, 1)",
+    "RaviartThomas(T, 2)",
+    "RaviartThomas(T, 3)",
+    "RaviartThomas(S, 1)",
+    "RaviartThomas(S, 2)",
+    "RaviartThomas(S, 3)",
+    "DiscontinuousRaviartThomas(T, 1)",
+    "DiscontinuousRaviartThomas(T, 2)",
+    "DiscontinuousRaviartThomas(T, 3)",
+    "DiscontinuousRaviartThomas(S, 1)",
+    "DiscontinuousRaviartThomas(S, 2)",
+    "DiscontinuousRaviartThomas(S, 3)",
+    "BrezziDouglasMarini(T, 1)",
+    "BrezziDouglasMarini(T, 2)",
+    "BrezziDouglasMarini(T, 3)",
+    "BrezziDouglasMarini(S, 1)",
+    "BrezziDouglasMarini(S, 2)",
+    "BrezziDouglasMarini(S, 3)",
+    "Nedelec(T, 1)",
+    "Nedelec(T, 2)",
+    "Nedelec(T, 3)",
+    "Nedelec(S, 1)",
+    "Nedelec(S, 2)",
+    "Nedelec(S, 3)",
+    "NedelecSecondKind(T, 1)",
+    "NedelecSecondKind(T, 2)",
+    "NedelecSecondKind(T, 3)",
+    "NedelecSecondKind(S, 1)",
+    "NedelecSecondKind(S, 2)",
+    "NedelecSecondKind(S, 3)",
+    "Regge(T, 0)",
+    "Regge(T, 1)",
+    "Regge(T, 2)",
+    "Regge(S, 0)",
+    "Regge(S, 1)",
+    "Regge(S, 2)",
+    "HellanHerrmannJohnson(T, 0)",
+    "HellanHerrmannJohnson(T, 1)",
+    "HellanHerrmannJohnson(T, 2)",
+    "BrezziDouglasFortinMarini(T, 2)",
+    "GaussLegendre(I, 0)",
+    "GaussLegendre(I, 1)",
+    "GaussLegendre(I, 2)",
+    "GaussLobattoLegendre(I, 1)",
+    "GaussLobattoLegendre(I, 2)",
+    "GaussLobattoLegendre(I, 3)",
+    "Bubble(I, 2)",
+    "Bubble(T, 3)",
+    "Bubble(S, 4)",
+    "RestrictedElement(Lagrange(I, 2), restriction_domain='facet')",
+    "RestrictedElement(Lagrange(T, 2), restriction_domain='vertex')",
+    "RestrictedElement(Lagrange(T, 3), restriction_domain='facet')",
+    "NodalEnrichedElement(Lagrange(I, 1), Bubble(I, 2))",
+    "NodalEnrichedElement(Lagrange(T, 1), Bubble(T, 3))",
+    "NodalEnrichedElement(Lagrange(S, 1), Bubble(S, 4))",
+    "NodalEnrichedElement("
+    "    RaviartThomas(T, 1),"
+    "    RestrictedElement(RaviartThomas(T, 2), restriction_domain='interior')"
+    ")",
+    "NodalEnrichedElement("
+    "    Regge(S, 1),"
+    "    RestrictedElement(Regge(S, 2), restriction_domain='interior')"
+    ")",
+
+    # Following element do not bother implementing get_nodal_basis
+    # so the test would need to be rewritten using tabulate
+    xfail_impl("TensorProductElement(DiscontinuousLagrange(I, 1), Lagrange(I, 2))"),
+    xfail_impl("Hdiv(TensorProductElement(DiscontinuousLagrange(I, 1), Lagrange(I, 2)))"),
+    xfail_impl("Hcurl(TensorProductElement(DiscontinuousLagrange(I, 1), Lagrange(I, 2)))"),
+    xfail_impl("HDivTrace(T, 1)"),
+    xfail_impl("EnrichedElement("
+               "Hdiv(TensorProductElement(Lagrange(I, 1), DiscontinuousLagrange(I, 0))), "
+               "Hdiv(TensorProductElement(DiscontinuousLagrange(I, 0), Lagrange(I, 1)))"
+               ")"),
+    xfail_impl("EnrichedElement("
+               "Hcurl(TensorProductElement(Lagrange(I, 1), DiscontinuousLagrange(I, 0))), "
+               "Hcurl(TensorProductElement(DiscontinuousLagrange(I, 0), Lagrange(I, 1)))"
+               ")"),
+
+    # These elements have broken constructor
+    xfail_key("Argyris(T, 1)",),
+    xfail_key("QuinticArgyris(T)",),
+    xfail_key("CubicHermite(I)",),
+    xfail_key("CubicHermite(T)",),
+    xfail_key("CubicHermite(S)",),
+    xfail_key("Morley(T)",),
+]
+
+
+ at pytest.mark.parametrize('element', elements)
+def test_nodality(element):
+    """Check that generated elements are nodal, i.e. nodes evaluated
+    on basis functions give Kronecker delta
+    """
+    # Instantiate element lazily
+    element = eval(element)
+
+    # Fetch primal and dual basis
+    poly_set = element.get_nodal_basis()
+    dual_set = element.get_dual_set()
+    assert poly_set.get_reference_element() == dual_set.get_reference_element()
+
+    # Get coeffs of primal and dual bases w.r.t. expansion set
+    coeffs_poly = poly_set.get_coeffs()
+    coeffs_dual = dual_set.to_riesz(poly_set)
+    assert coeffs_poly.shape == coeffs_dual.shape
+
+    # Check nodality
+    for i in range(coeffs_dual.shape[0]):
+        for j in range(coeffs_poly.shape[0]):
+            assert np.isclose(
+                coeffs_dual[i].flatten().dot(coeffs_poly[j].flatten()),
+                1.0 if i == j else 0.0
+            )
+
+
+ at pytest.mark.parametrize('elements', [
+    (Lagrange(I, 2), Bubble(I, 2)),
+    (Lagrange(T, 3), Bubble(T, 3)),
+    (Lagrange(S, 4), Bubble(S, 4)),
+    (Lagrange(I, 1), Lagrange(I, 1)),
+    (Lagrange(I, 1), Bubble(I, 2), Bubble(I, 2)),
+])
+def test_illposed_nodal_enriched(elements):
+    """Check that nodal enriched element fails on ill-posed
+    (non-unisolvent) case
+    """
+    with pytest.raises(np.linalg.LinAlgError):
+        NodalEnrichedElement(*elements)
+
+
+def test_empty_bubble():
+    "Check that bubble of too low degree fails"
+    with pytest.raises(RuntimeError):
+        Bubble(I, 1)
+    with pytest.raises(RuntimeError):
+        Bubble(T, 2)
+    with pytest.raises(RuntimeError):
+        Bubble(S, 3)
+
+
+def test_nodal_enriched_implementation():
+    """Following element pair should be the same.
+    This might be fragile to dof reordering but works now.
+    """
+
+    e0 = RaviartThomas(T, 2)
+
+    e1 = NodalEnrichedElement(
+        RestrictedElement(RaviartThomas(T, 2), restriction_domain='facet'),
+        RestrictedElement(RaviartThomas(T, 2), restriction_domain='interior')
+    )
+
+    for attr in ["degree",
+                 "get_reference_element",
+                 "entity_dofs",
+                 "entity_closure_dofs",
+                 "get_formdegree",
+                 "mapping",
+                 "num_sub_elements",
+                 "space_dimension",
+                 "value_shape",
+                 "is_nodal",
+                 ]:
+        assert getattr(e0, attr)() == getattr(e1, attr)()
+    assert np.allclose(e0.get_coeffs(), e1.get_coeffs())
+    assert np.allclose(e0.dmats(), e1.dmats())
+    assert np.allclose(e0.get_dual_set().to_riesz(e0.get_nodal_basis()),
+                       e1.get_dual_set().to_riesz(e1.get_nodal_basis()))
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_gauss_legendre.py b/test/unit/test_gauss_legendre.py
new file mode 100644
index 0000000..950fbc0
--- /dev/null
+++ b/test/unit/test_gauss_legendre.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2016 Imperial College London and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#
+# David Ham
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import numpy as np
+
+
+ at pytest.mark.parametrize("degree", range(1, 7))
+def test_gl_basis_values(degree):
+    """Ensure that integrating a simple monomial produces the expected results."""
+    from FIAT import ufc_simplex, GaussLegendre, make_quadrature
+
+    s = ufc_simplex(1)
+    q = make_quadrature(s, degree + 1)
+
+    fe = GaussLegendre(s, degree)
+    tab = fe.tabulate(0, q.pts)[(0,)]
+
+    for test_degree in range(degree + 1):
+        coefs = [n(lambda x: x[0]**test_degree) for n in fe.dual.nodes]
+        integral = np.dot(coefs, np.dot(tab, q.wts))
+        reference = np.dot([x[0]**test_degree
+                            for x in q.pts], q.wts)
+        assert np.allclose(integral, reference, rtol=1e-14)
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_gauss_lobatto_legendre.py b/test/unit/test_gauss_lobatto_legendre.py
new file mode 100644
index 0000000..3a17903
--- /dev/null
+++ b/test/unit/test_gauss_lobatto_legendre.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2016 Imperial College London and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#
+# David Ham
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import numpy as np
+
+
+ at pytest.mark.parametrize("degree", range(1, 7))
+def test_gll_basis_values(degree):
+    """Ensure that integrating a simple monomial produces the expected results."""
+    from FIAT import ufc_simplex, GaussLobattoLegendre, make_quadrature
+
+    s = ufc_simplex(1)
+    q = make_quadrature(s, degree + 1)
+
+    fe = GaussLobattoLegendre(s, degree)
+    tab = fe.tabulate(0, q.pts)[(0,)]
+
+    for test_degree in range(degree + 1):
+        coefs = [n(lambda x: x[0]**test_degree) for n in fe.dual.nodes]
+        integral = np.dot(coefs, np.dot(tab, q.wts))
+        reference = np.dot([x[0]**test_degree
+                            for x in q.pts], q.wts)
+        assert np.allclose(integral, reference, rtol=1e-14)
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_hdivtrace.py b/test/unit/test_hdivtrace.py
new file mode 100644
index 0000000..cfd96d9
--- /dev/null
+++ b/test/unit/test_hdivtrace.py
@@ -0,0 +1,111 @@
+# Copyright (C) 2016 Imperial College London and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#
+# Thomas Gibson (t.gibson15 at imperial.ac.uk)
+
+from __future__ import absolute_import, print_function, division
+from six.moves import map
+
+import pytest
+import numpy as np
+
+
+ at pytest.mark.parametrize("dim", (2, 3))
+ at pytest.mark.parametrize("degree", range(7))
+def test_basis_values(dim, degree):
+    """Ensure that integrating simple monomials produces the expected results
+    for each facet entity of the reference triangle and tetrahedron.
+
+    This test performs the trace tabulation in two ways:
+    (1) The entity is not specified, in which case the element uses
+        numerical tolerance to determine the facet id;
+    (2) The entity pair (dim, id) is provided, and the trace element
+        tabulates accordingly using the new tabulate API.
+    """
+    from FIAT import ufc_simplex, HDivTrace, make_quadrature
+
+    ref_el = ufc_simplex(dim)
+    quadrule = make_quadrature(ufc_simplex(dim - 1), degree + 1)
+    fiat_element = HDivTrace(ref_el, degree)
+    nf = fiat_element.facet_element.space_dimension()
+
+    for facet_id in range(dim + 1):
+        # Tabulate without an entity pair given --- need to map to cell coordinates
+        cell_transform = ref_el.get_entity_transform(dim - 1, facet_id)
+        cell_points = np.array(list(map(cell_transform, quadrule.pts)))
+        ctab = fiat_element.tabulate(0, cell_points)[(0,) * dim][nf*facet_id:nf*(facet_id + 1)]
+
+        # Tabulate with entity pair provided
+        entity = (ref_el.get_spatial_dimension() - 1, facet_id)
+        etab = fiat_element.tabulate(0, quadrule.pts,
+                                     entity)[(0,) * dim][nf*facet_id:nf*(facet_id + 1)]
+
+        for test_degree in range(degree + 1):
+            coeffs = [n(lambda x: x[0]**test_degree)
+                      for n in fiat_element.facet_element.dual.nodes]
+
+            cintegral = np.dot(coeffs, np.dot(ctab, quadrule.wts))
+            eintegral = np.dot(coeffs, np.dot(etab, quadrule.wts))
+            assert np.allclose(cintegral, eintegral, rtol=1e-14)
+
+            reference = np.dot([x[0]**test_degree
+                                for x in quadrule.pts], quadrule.wts)
+            assert np.allclose(cintegral, reference, rtol=1e-14)
+            assert np.allclose(eintegral, reference, rtol=1e-14)
+
+
+ at pytest.mark.parametrize("dim", (2, 3))
+ at pytest.mark.parametrize("order", range(1, 4))
+ at pytest.mark.parametrize("degree", range(4))
+def test_gradient_traceerror(dim, order, degree):
+    """Ensure that the TraceError appears in the appropriate dict entries when
+    attempting to tabulate certain orders of derivatives."""
+    from FIAT import ufc_simplex, HDivTrace, make_quadrature
+    from FIAT.hdiv_trace import TraceError
+
+    fiat_element = HDivTrace(ufc_simplex(dim), degree)
+    pts = make_quadrature(ufc_simplex(dim - 1), degree + 1).pts
+
+    for facet_id in range(dim + 1):
+        tab = fiat_element.tabulate(order, pts, entity=(dim - 1, facet_id))
+
+        for key in tab.keys():
+            if key != (0,)*dim:
+                assert isinstance(tab[key], TraceError)
+
+
+ at pytest.mark.parametrize("dim", (2, 3))
+ at pytest.mark.parametrize("degree", range(4))
+def test_cell_traceerror(dim, degree):
+    """Ensure that the TraceError appears in all dict entries when deliberately
+    attempting to tabulate the cell of a trace element."""
+    from FIAT import ufc_simplex, HDivTrace, make_quadrature
+    from FIAT.hdiv_trace import TraceError
+
+    fiat_element = HDivTrace(ufc_simplex(dim), degree)
+    pts = make_quadrature(ufc_simplex(dim), 1).pts
+    tab = fiat_element.tabulate(0, pts, entity=(dim, 0))
+
+    for key in tab.keys():
+        assert isinstance(tab[key], TraceError)
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_quadrature.py b/test/unit/test_quadrature.py
new file mode 100644
index 0000000..149c3df
--- /dev/null
+++ b/test/unit/test_quadrature.py
@@ -0,0 +1,195 @@
+# Copyright (C) 2015 Imperial College London and others.
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Written by David A. Ham (david.ham at imperial.ac.uk), 2015
+
+from __future__ import absolute_import, print_function, division
+
+import numpy
+import pytest
+import FIAT
+from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron
+from FIAT.reference_element import FiredrakeQuadrilateral, TensorProductCell
+
+
+ at pytest.fixture(scope='module')
+def interval():
+    return UFCInterval()
+
+
+ at pytest.fixture(scope='module')
+def triangle():
+    return UFCTriangle()
+
+
+ at pytest.fixture(scope='module')
+def tetrahedron():
+    return UFCTetrahedron()
+
+
+ at pytest.fixture(scope='module')
+def quadrilateral():
+    return FiredrakeQuadrilateral()
+
+
+ at pytest.fixture(scope='module')
+def extr_interval():
+    """Extruded interval = interval x interval"""
+    return TensorProductCell(UFCInterval(), UFCInterval())
+
+
+ at pytest.fixture(scope='module')
+def extr_triangle():
+    """Extruded triangle = triangle x interval"""
+    return TensorProductCell(UFCTriangle(), UFCInterval())
+
+
+ at pytest.fixture(scope='module')
+def extr_quadrilateral():
+    """Extruded quadrilateral = quadrilateral x interval"""
+    return TensorProductCell(FiredrakeQuadrilateral(), UFCInterval())
+
+
+ at pytest.fixture(params=["canonical", "default"])
+def scheme(request):
+    return request.param
+
+
+def test_invalid_quadrature_rule():
+    from FIAT.quadrature import QuadratureRule
+    with pytest.raises(ValueError):
+        QuadratureRule(UFCInterval(), [[0.5, 0.5]], [0.5, 0.5, 0.5])
+
+
+ at pytest.mark.parametrize("degree", range(8))
+def test_create_quadrature_interval(interval, degree, scheme):
+    q = FIAT.create_quadrature(interval, degree, scheme)
+    assert numpy.allclose(q.integrate(lambda x: x[0]**degree), 1/(degree + 1))
+
+
+ at pytest.mark.parametrize("degree", range(8))
+def test_create_quadrature_triangle(triangle, degree, scheme):
+    q = FIAT.create_quadrature(triangle, degree, scheme)
+    assert numpy.allclose(q.integrate(lambda x: sum(x)**degree), 1/(degree + 2))
+
+
+ at pytest.mark.parametrize("degree", range(8))
+def test_create_quadrature_tetrahedron(tetrahedron, degree, scheme):
+    q = FIAT.create_quadrature(tetrahedron, degree, scheme)
+    assert numpy.allclose(q.integrate(lambda x: sum(x)**degree), 1/(2*degree + 6))
+
+
+ at pytest.mark.parametrize("extrdeg", range(4))
+ at pytest.mark.parametrize("basedeg", range(5))
+def test_create_quadrature_extr_interval(extr_interval, basedeg, extrdeg, scheme):
+    q = FIAT.create_quadrature(extr_interval, (basedeg, extrdeg), scheme)
+    assert numpy.allclose(q.integrate(lambda x: x[0]**basedeg * x[1]**extrdeg),
+                          1/(basedeg + 1) * 1/(extrdeg + 1))
+
+
+ at pytest.mark.parametrize("extrdeg", range(4))
+ at pytest.mark.parametrize("basedeg", range(5))
+def test_create_quadrature_extr_triangle(extr_triangle, basedeg, extrdeg, scheme):
+    q = FIAT.create_quadrature(extr_triangle, (basedeg, extrdeg), scheme)
+    assert numpy.allclose(q.integrate(lambda x: (x[0] + x[1])**basedeg * x[2]**extrdeg),
+                          1/(basedeg + 2) * 1/(extrdeg + 1))
+
+
+ at pytest.mark.parametrize("degree", range(8))
+def test_create_quadrature_quadrilateral(quadrilateral, degree, scheme):
+    q = FIAT.create_quadrature(quadrilateral, degree, scheme)
+    assert numpy.allclose(q.integrate(lambda x: sum(x)**degree),
+                          (2**(degree + 2) - 2) / ((degree + 1)*(degree + 2)))
+
+
+ at pytest.mark.parametrize("extrdeg", range(4))
+ at pytest.mark.parametrize("basedeg", range(5))
+def test_create_quadrature_extr_quadrilateral(extr_quadrilateral, basedeg, extrdeg, scheme):
+    q = FIAT.create_quadrature(extr_quadrilateral, (basedeg, extrdeg), scheme)
+    assert numpy.allclose(q.integrate(lambda x: (x[0] + x[1])**basedeg * x[2]**extrdeg),
+                          (2**(basedeg + 2) - 2) / ((basedeg + 1)*(basedeg + 2)) * 1/(extrdeg + 1))
+
+
+ at pytest.mark.parametrize("cell", [interval(),
+                                  triangle(),
+                                  tetrahedron(),
+                                  quadrilateral()])
+def test_invalid_quadrature_degree(cell, scheme):
+    with pytest.raises(ValueError):
+        FIAT.create_quadrature(cell, -1, scheme)
+
+
+ at pytest.mark.parametrize("cell", [extr_interval(),
+                                  extr_triangle(),
+                                  extr_quadrilateral()])
+def test_invalid_quadrature_degree_tensor_prod(cell):
+    with pytest.raises(ValueError):
+        FIAT.create_quadrature(cell, (-1, -1))
+
+
+ at pytest.mark.parametrize("cell", [interval(),
+                                  triangle(),
+                                  tetrahedron(),
+                                  quadrilateral()])
+def test_high_degree_runtime_error(cell):
+    with pytest.raises(RuntimeError):
+        FIAT.create_quadrature(cell, 60)
+
+
+ at pytest.mark.parametrize("cell", [extr_interval(),
+                                  extr_triangle(),
+                                  extr_quadrilateral()])
+def test_high_degree_runtime_error_tensor_prod(cell):
+    with pytest.raises(RuntimeError):
+        FIAT.create_quadrature(cell, (60, 60))
+
+
+def test_tensor_product_composition(interval, triangle, extr_triangle, scheme):
+    degree = (4, 4)
+    qa = FIAT.create_quadrature(triangle, degree[0], scheme)
+    qb = FIAT.create_quadrature(interval, degree[1], scheme)
+    q = FIAT.create_quadrature(extr_triangle, degree, scheme)
+    assert len(q.get_points()) == len(qa.get_points())*len(qb.get_points())
+
+
+ at pytest.mark.parametrize(("points, degree"), ((p, d)
+                                              for p in range(2, 10)
+                                              for d in range(2*p - 2)))
+def test_gauss_lobatto_legendre_quadrature(interval, points, degree):
+    """Check that the quadrature rules correctly integrate all the right
+    polynomial degrees."""
+
+    q = FIAT.quadrature.GaussLobattoLegendreQuadratureLineRule(interval, points)
+
+    assert numpy.round(q.integrate(lambda x: x[0]**degree) - 1./(degree+1), 14) == 0.
+
+
+ at pytest.mark.parametrize(("points, degree"), ((p, d)
+                                              for p in range(2, 10)
+                                              for d in range(2*p)))
+def test_gauss_legendre_quadrature(interval, points, degree):
+    """Check that the quadrature rules correctly integrate all the right
+    polynomial degrees."""
+
+    q = FIAT.quadrature.GaussLegendreQuadratureLineRule(interval, points)
+
+    assert numpy.round(q.integrate(lambda x: x[0]**degree) - 1./(degree+1), 14) == 0.
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_reference_element.py b/test/unit/test_reference_element.py
new file mode 100644
index 0000000..4034f80
--- /dev/null
+++ b/test/unit/test_reference_element.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2016 Miklos Homolya
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import numpy as np
+
+from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron
+from FIAT.reference_element import Point, FiredrakeQuadrilateral, TensorProductCell
+
+point = Point()
+interval = UFCInterval()
+triangle = UFCTriangle()
+quadrilateral = FiredrakeQuadrilateral()
+tetrahedron = UFCTetrahedron()
+interval_x_interval = TensorProductCell(interval, interval)
+triangle_x_interval = TensorProductCell(triangle, interval)
+quadrilateral_x_interval = TensorProductCell(quadrilateral, interval)
+
+
+ at pytest.mark.parametrize(('cell', 'volume'),
+                         [pytest.mark.xfail((point, 1)),
+                          (interval, 1),
+                          (triangle, 1/2),
+                          (quadrilateral, 1),
+                          (tetrahedron, 1/6),
+                          (interval_x_interval, 1),
+                          (triangle_x_interval, 1/2),
+                          (quadrilateral_x_interval, 1)])
+def test_volume(cell, volume):
+    assert np.allclose(volume, cell.volume())
+
+
+ at pytest.mark.parametrize(('cell', 'normals'),
+                         [(interval, [[-1],
+                                      [1]]),
+                          (triangle, [[1, 1],
+                                      [-1, 0],
+                                      [0, -1]]),
+                          (quadrilateral, [[-1, 0],
+                                           [1, 0],
+                                           [0, -1],
+                                           [0, 1]]),
+                          (tetrahedron, [[1, 1, 1],
+                                         [-1, 0, 0],
+                                         [0, -1, 0],
+                                         [0, 0, -1]])])
+def test_reference_normal(cell, normals):
+    facet_dim = cell.get_spatial_dimension() - 1
+    for facet_number in range(len(cell.get_topology()[facet_dim])):
+        assert np.allclose(normals[facet_number],
+                           cell.compute_reference_normal(facet_dim, facet_number))
+
+
+ at pytest.mark.parametrize('cell',
+                         [interval_x_interval,
+                          triangle_x_interval,
+                          quadrilateral_x_interval])
+def test_reference_normal_horiz(cell):
+    dim = cell.get_spatial_dimension()
+    np.allclose((0,) * (dim - 1) + (-1,),
+                cell.compute_reference_normal((dim - 1, 0), 0))  # bottom facet
+    np.allclose((0,) * (dim - 1) + (1,),
+                cell.compute_reference_normal((dim - 1, 0), 1))  # top facet
+
+
+ at pytest.mark.parametrize(('cell', 'normals'),
+                         [(interval_x_interval, [[-1, 0],
+                                                 [1, 0]]),
+                          (triangle_x_interval, [[1, 1, 0],
+                                                 [-1, 0, 0],
+                                                 [0, -1, 0]]),
+                          (quadrilateral_x_interval, [[-1, 0, 0],
+                                                      [1, 0, 0],
+                                                      [0, -1, 0],
+                                                      [0, 1, 0]])])
+def test_reference_normal_vert(cell, normals):
+    dim = cell.get_spatial_dimension()
+    vert_dim = (dim - 2, 1)
+    for facet_number in range(len(cell.get_topology()[vert_dim])):
+        assert np.allclose(normals[facet_number],
+                           cell.compute_reference_normal(vert_dim, facet_number))
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_regge_hhj.py b/test/unit/test_regge_hhj.py
new file mode 100644
index 0000000..b6a61ae
--- /dev/null
+++ b/test/unit/test_regge_hhj.py
@@ -0,0 +1,24 @@
+from __future__ import absolute_import, print_function, division
+from FIAT.reference_element import UFCTriangle
+from FIAT import Regge, HellanHerrmannJohnson
+import numpy as np
+import pytest
+
+
+def test_rotated_regge_is_hhj():
+    triangle = UFCTriangle()
+
+    R = Regge(triangle, 0)
+    H = HellanHerrmannJohnson(triangle, 0)
+
+    def S(u):
+        return np.eye(2) * np.trace(u) - u
+
+    for (r, h) in zip(R.tabulate(0, (0.2, 0.2))[(0, 0)],
+                      H.tabulate(0, (0.2, 0.2))[(0, 0)]):
+        assert np.all(np.isclose(r, S(h)))
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))
diff --git a/test/unit/test_tensor_product.py b/test/unit/test_tensor_product.py
new file mode 100644
index 0000000..9c20ba7
--- /dev/null
+++ b/test/unit/test_tensor_product.py
@@ -0,0 +1,527 @@
+# Copyright (C) 2015-2016 Imperial College London and others
+#
+# This file is part of FIAT.
+#
+# FIAT is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FIAT is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#
+# Andrew McRae
+
+from __future__ import absolute_import, print_function, division
+
+import pytest
+import numpy as np
+
+from FIAT.reference_element import UFCInterval, UFCTriangle
+from FIAT.lagrange import Lagrange
+from FIAT.discontinuous_lagrange import DiscontinuousLagrange
+from FIAT.nedelec import Nedelec
+from FIAT.raviart_thomas import RaviartThomas
+from FIAT.tensor_product import TensorProductElement
+from FIAT.hdivcurl import Hdiv, Hcurl
+from FIAT.enriched import EnrichedElement
+
+
+def test_TFE_1Dx1D_scalar():
+    T = UFCInterval()
+    P1_DG = DiscontinuousLagrange(T, 1)
+    P2 = Lagrange(T, 2)
+
+    elt = TensorProductElement(P1_DG, P2)
+    assert elt.value_shape() == ()
+    tab = elt.tabulate(1, [(0.1, 0.2)])
+    tabA = P1_DG.tabulate(1, [(0.1,)])
+    tabB = P2.tabulate(1, [(0.2,)])
+    for da, db in [[(0,), (0,)], [(1,), (0,)], [(0,), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0], tabA[da][0][0]*tabB[db][2][0])
+        assert np.isclose(tab[dc][3][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][4][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][5][0], tabA[da][1][0]*tabB[db][2][0])
+
+
+def test_TFE_1Dx1D_vector():
+    T = UFCInterval()
+    P1_DG = DiscontinuousLagrange(T, 1)
+    P2 = Lagrange(T, 2)
+
+    elt = TensorProductElement(P1_DG, P2)
+    hdiv_elt = Hdiv(elt)
+    hcurl_elt = Hcurl(elt)
+    assert hdiv_elt.value_shape() == (2,)
+    assert hcurl_elt.value_shape() == (2,)
+
+    tabA = P1_DG.tabulate(1, [(0.1,)])
+    tabB = P2.tabulate(1, [(0.2,)])
+
+    hdiv_tab = hdiv_elt.tabulate(1, [(0.1, 0.2)])
+    for da, db in [[(0,), (0,)], [(1,), (0,)], [(0,), (1,)]]:
+        dc = da + db
+        assert hdiv_tab[dc][0][0][0] == 0.0
+        assert hdiv_tab[dc][1][0][0] == 0.0
+        assert hdiv_tab[dc][2][0][0] == 0.0
+        assert hdiv_tab[dc][3][0][0] == 0.0
+        assert hdiv_tab[dc][4][0][0] == 0.0
+        assert hdiv_tab[dc][5][0][0] == 0.0
+        assert np.isclose(hdiv_tab[dc][0][1][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(hdiv_tab[dc][1][1][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(hdiv_tab[dc][2][1][0], tabA[da][0][0]*tabB[db][2][0])
+        assert np.isclose(hdiv_tab[dc][3][1][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(hdiv_tab[dc][4][1][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(hdiv_tab[dc][5][1][0], tabA[da][1][0]*tabB[db][2][0])
+
+    hcurl_tab = hcurl_elt.tabulate(1, [(0.1, 0.2)])
+    for da, db in [[(0,), (0,)], [(1,), (0,)], [(0,), (1,)]]:
+        dc = da + db
+        assert np.isclose(hcurl_tab[dc][0][0][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(hcurl_tab[dc][1][0][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(hcurl_tab[dc][2][0][0], tabA[da][0][0]*tabB[db][2][0])
+        assert np.isclose(hcurl_tab[dc][3][0][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(hcurl_tab[dc][4][0][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(hcurl_tab[dc][5][0][0], tabA[da][1][0]*tabB[db][2][0])
+        assert hcurl_tab[dc][0][1][0] == 0.0
+        assert hcurl_tab[dc][1][1][0] == 0.0
+        assert hcurl_tab[dc][2][1][0] == 0.0
+        assert hcurl_tab[dc][3][1][0] == 0.0
+        assert hcurl_tab[dc][4][1][0] == 0.0
+        assert hcurl_tab[dc][5][1][0] == 0.0
+
+
+def test_TFE_2Dx1D_scalar_triangle():
+    S = UFCTriangle()
+    T = UFCInterval()
+    P1_DG = DiscontinuousLagrange(S, 1)
+    P2 = Lagrange(T, 2)
+
+    elt = TensorProductElement(P1_DG, P2)
+    assert elt.value_shape() == ()
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = P1_DG.tabulate(1, [(0.1, 0.2)])
+    tabB = P2.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0], tabA[da][0][0]*tabB[db][2][0])
+        assert np.isclose(tab[dc][3][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][4][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][5][0], tabA[da][1][0]*tabB[db][2][0])
+        assert np.isclose(tab[dc][6][0], tabA[da][2][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][7][0], tabA[da][2][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][8][0], tabA[da][2][0]*tabB[db][2][0])
+
+
+def test_TFE_2Dx1D_scalar_quad():
+    T = UFCInterval()
+    P1 = Lagrange(T, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = TensorProductElement(TensorProductElement(P1, P1_DG), P1)
+    assert elt.value_shape() == ()
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tA = P1.tabulate(1, [(0.1,)])
+    tB = P1_DG.tabulate(1, [(0.2,)])
+    tC = P1.tabulate(1, [(0.3,)])
+    for da, db, dc in [[(0,), (0,), (0,)], [(1,), (0,), (0,)], [(0,), (1,), (0,)], [(0,), (0,), (1,)]]:
+        dd = da + db + dc
+        assert np.isclose(tab[dd][0][0], tA[da][0][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][1][0], tA[da][0][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][2][0], tA[da][0][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][3][0], tA[da][0][0]*tB[db][1][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][4][0], tA[da][1][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][5][0], tA[da][1][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][6][0], tA[da][1][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][7][0], tA[da][1][0]*tB[db][1][0]*tC[dc][1][0])
+
+
+def test_TFE_2Dx1D_scalar_triangle_hdiv():
+    S = UFCTriangle()
+    T = UFCInterval()
+    P1_DG = DiscontinuousLagrange(S, 1)
+    P2 = Lagrange(T, 2)
+
+    elt = Hdiv(TensorProductElement(P1_DG, P2))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = P1_DG.tabulate(1, [(0.1, 0.2)])
+    tabB = P2.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert tab[dc][0][0][0] == 0.0
+        assert tab[dc][1][0][0] == 0.0
+        assert tab[dc][2][0][0] == 0.0
+        assert tab[dc][3][0][0] == 0.0
+        assert tab[dc][4][0][0] == 0.0
+        assert tab[dc][5][0][0] == 0.0
+        assert tab[dc][6][0][0] == 0.0
+        assert tab[dc][7][0][0] == 0.0
+        assert tab[dc][8][0][0] == 0.0
+        assert tab[dc][0][1][0] == 0.0
+        assert tab[dc][1][1][0] == 0.0
+        assert tab[dc][2][1][0] == 0.0
+        assert tab[dc][3][1][0] == 0.0
+        assert tab[dc][4][1][0] == 0.0
+        assert tab[dc][5][1][0] == 0.0
+        assert tab[dc][6][1][0] == 0.0
+        assert tab[dc][7][1][0] == 0.0
+        assert tab[dc][8][1][0] == 0.0
+        assert np.isclose(tab[dc][0][2][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][2][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][2][0], tabA[da][0][0]*tabB[db][2][0])
+        assert np.isclose(tab[dc][3][2][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][4][2][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][5][2][0], tabA[da][1][0]*tabB[db][2][0])
+        assert np.isclose(tab[dc][6][2][0], tabA[da][2][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][7][2][0], tabA[da][2][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][8][2][0], tabA[da][2][0]*tabB[db][2][0])
+
+
+def test_TFE_2Dx1D_scalar_triangle_hcurl():
+    S = UFCTriangle()
+    T = UFCInterval()
+    P1 = Lagrange(S, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = Hcurl(TensorProductElement(P1, P1_DG))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = P1.tabulate(1, [(0.1, 0.2)])
+    tabB = P1_DG.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert tab[dc][0][0][0] == 0.0
+        assert tab[dc][1][0][0] == 0.0
+        assert tab[dc][2][0][0] == 0.0
+        assert tab[dc][3][0][0] == 0.0
+        assert tab[dc][4][0][0] == 0.0
+        assert tab[dc][5][0][0] == 0.0
+        assert tab[dc][0][1][0] == 0.0
+        assert tab[dc][1][1][0] == 0.0
+        assert tab[dc][2][1][0] == 0.0
+        assert tab[dc][3][1][0] == 0.0
+        assert tab[dc][4][1][0] == 0.0
+        assert tab[dc][5][1][0] == 0.0
+        assert np.isclose(tab[dc][0][2][0], tabA[da][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][2][0], tabA[da][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][2][0], tabA[da][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][2][0], tabA[da][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][2][0], tabA[da][2][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][2][0], tabA[da][2][0]*tabB[db][1][0])
+
+
+def test_TFE_2Dx1D_scalar_quad_hdiv():
+    T = UFCInterval()
+    P1 = Lagrange(T, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = Hdiv(TensorProductElement(TensorProductElement(P1_DG, P1_DG), P1))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tA = P1_DG.tabulate(1, [(0.1,)])
+    tB = P1_DG.tabulate(1, [(0.2,)])
+    tC = P1.tabulate(1, [(0.3,)])
+    for da, db, dc in [[(0,), (0,), (0,)], [(1,), (0,), (0,)], [(0,), (1,), (0,)], [(0,), (0,), (1,)]]:
+        dd = da + db + dc
+        assert tab[dd][0][0][0] == 0.0
+        assert tab[dd][1][0][0] == 0.0
+        assert tab[dd][2][0][0] == 0.0
+        assert tab[dd][3][0][0] == 0.0
+        assert tab[dd][4][0][0] == 0.0
+        assert tab[dd][5][0][0] == 0.0
+        assert tab[dd][6][0][0] == 0.0
+        assert tab[dd][7][0][0] == 0.0
+        assert tab[dd][0][1][0] == 0.0
+        assert tab[dd][1][1][0] == 0.0
+        assert tab[dd][2][1][0] == 0.0
+        assert tab[dd][3][1][0] == 0.0
+        assert tab[dd][4][1][0] == 0.0
+        assert tab[dd][5][1][0] == 0.0
+        assert tab[dd][6][1][0] == 0.0
+        assert tab[dd][7][1][0] == 0.0
+        assert np.isclose(tab[dd][0][2][0], tA[da][0][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][1][2][0], tA[da][0][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][2][2][0], tA[da][0][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][3][2][0], tA[da][0][0]*tB[db][1][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][4][2][0], tA[da][1][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][5][2][0], tA[da][1][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][6][2][0], tA[da][1][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][7][2][0], tA[da][1][0]*tB[db][1][0]*tC[dc][1][0])
+
+
+def test_TFE_2Dx1D_scalar_quad_hcurl():
+    T = UFCInterval()
+    P1 = Lagrange(T, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = Hcurl(TensorProductElement(TensorProductElement(P1, P1), P1_DG))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tA = P1.tabulate(1, [(0.1,)])
+    tB = P1.tabulate(1, [(0.2,)])
+    tC = P1_DG.tabulate(1, [(0.3,)])
+    for da, db, dc in [[(0,), (0,), (0,)], [(1,), (0,), (0,)], [(0,), (1,), (0,)], [(0,), (0,), (1,)]]:
+        dd = da + db + dc
+        assert tab[dd][0][0][0] == 0.0
+        assert tab[dd][1][0][0] == 0.0
+        assert tab[dd][2][0][0] == 0.0
+        assert tab[dd][3][0][0] == 0.0
+        assert tab[dd][4][0][0] == 0.0
+        assert tab[dd][5][0][0] == 0.0
+        assert tab[dd][6][0][0] == 0.0
+        assert tab[dd][7][0][0] == 0.0
+        assert tab[dd][0][1][0] == 0.0
+        assert tab[dd][1][1][0] == 0.0
+        assert tab[dd][2][1][0] == 0.0
+        assert tab[dd][3][1][0] == 0.0
+        assert tab[dd][4][1][0] == 0.0
+        assert tab[dd][5][1][0] == 0.0
+        assert tab[dd][6][1][0] == 0.0
+        assert tab[dd][7][1][0] == 0.0
+        assert np.isclose(tab[dd][0][2][0], tA[da][0][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][1][2][0], tA[da][0][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][2][2][0], tA[da][0][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][3][2][0], tA[da][0][0]*tB[db][1][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][4][2][0], tA[da][1][0]*tB[db][0][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][5][2][0], tA[da][1][0]*tB[db][0][0]*tC[dc][1][0])
+        assert np.isclose(tab[dd][6][2][0], tA[da][1][0]*tB[db][1][0]*tC[dc][0][0])
+        assert np.isclose(tab[dd][7][2][0], tA[da][1][0]*tB[db][1][0]*tC[dc][1][0])
+
+
+def test_TFE_2Dx1D_vector_triangle_hdiv():
+    S = UFCTriangle()
+    T = UFCInterval()
+    RT1 = RaviartThomas(S, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = Hdiv(TensorProductElement(RT1, P1_DG))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = RT1.tabulate(1, [(0.1, 0.2)])
+    tabB = P1_DG.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0][0], tabA[da][0][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0][0], tabA[da][0][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0][0], tabA[da][1][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][0][0], tabA[da][1][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][0][0], tabA[da][2][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][0][0], tabA[da][2][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][0][1][0], tabA[da][0][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][1][0], tabA[da][0][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][1][0], tabA[da][1][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][1][0], tabA[da][1][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][1][0], tabA[da][2][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][1][0], tabA[da][2][1][0]*tabB[db][1][0])
+        assert tab[dc][0][2][0] == 0.0
+        assert tab[dc][1][2][0] == 0.0
+        assert tab[dc][2][2][0] == 0.0
+        assert tab[dc][3][2][0] == 0.0
+        assert tab[dc][4][2][0] == 0.0
+        assert tab[dc][5][2][0] == 0.0
+
+
+def test_TFE_2Dx1D_vector_triangle_hcurl():
+    S = UFCTriangle()
+    T = UFCInterval()
+    Ned1 = Nedelec(S, 1)
+    P1 = Lagrange(T, 1)
+
+    elt = Hcurl(TensorProductElement(Ned1, P1))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = Ned1.tabulate(1, [(0.1, 0.2)])
+    tabB = P1.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0][0], tabA[da][0][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0][0], tabA[da][0][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0][0], tabA[da][1][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][0][0], tabA[da][1][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][0][0], tabA[da][2][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][0][0], tabA[da][2][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][0][1][0], tabA[da][0][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][1][0], tabA[da][0][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][1][0], tabA[da][1][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][1][0], tabA[da][1][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][1][0], tabA[da][2][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][1][0], tabA[da][2][1][0]*tabB[db][1][0])
+        assert tab[dc][0][2][0] == 0.0
+        assert tab[dc][1][2][0] == 0.0
+        assert tab[dc][2][2][0] == 0.0
+        assert tab[dc][3][2][0] == 0.0
+        assert tab[dc][4][2][0] == 0.0
+        assert tab[dc][5][2][0] == 0.0
+
+
+def test_TFE_2Dx1D_vector_triangle_hdiv_rotate():
+    S = UFCTriangle()
+    T = UFCInterval()
+    Ned1 = Nedelec(S, 1)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    elt = Hdiv(TensorProductElement(Ned1, P1_DG))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = Ned1.tabulate(1, [(0.1, 0.2)])
+    tabB = P1_DG.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0][0], tabA[da][0][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0][0], tabA[da][0][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0][0], tabA[da][1][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][0][0], tabA[da][1][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][0][0], tabA[da][2][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][0][0], tabA[da][2][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][0][1][0], -tabA[da][0][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][1][0], -tabA[da][0][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][1][0], -tabA[da][1][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][1][0], -tabA[da][1][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][1][0], -tabA[da][2][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][1][0], -tabA[da][2][0][0]*tabB[db][1][0])
+        assert tab[dc][0][2][0] == 0.0
+        assert tab[dc][1][2][0] == 0.0
+        assert tab[dc][2][2][0] == 0.0
+        assert tab[dc][3][2][0] == 0.0
+        assert tab[dc][4][2][0] == 0.0
+        assert tab[dc][5][2][0] == 0.0
+
+
+def test_TFE_2Dx1D_vector_triangle_hcurl_rotate():
+    S = UFCTriangle()
+    T = UFCInterval()
+    RT1 = RaviartThomas(S, 1)
+    P1 = Lagrange(T, 1)
+
+    elt = Hcurl(TensorProductElement(RT1, P1))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tabA = RT1.tabulate(1, [(0.1, 0.2)])
+    tabB = P1.tabulate(1, [(0.3,)])
+    for da, db in [[(0, 0), (0,)], [(1, 0), (0,)], [(0, 1), (0,)], [(0, 0), (1,)]]:
+        dc = da + db
+        assert np.isclose(tab[dc][0][0][0], -tabA[da][0][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][0][0], -tabA[da][0][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][0][0], -tabA[da][1][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][0][0], -tabA[da][1][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][0][0], -tabA[da][2][1][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][0][0], -tabA[da][2][1][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][0][1][0], tabA[da][0][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][1][1][0], tabA[da][0][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][2][1][0], tabA[da][1][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][3][1][0], tabA[da][1][0][0]*tabB[db][1][0])
+        assert np.isclose(tab[dc][4][1][0], tabA[da][2][0][0]*tabB[db][0][0])
+        assert np.isclose(tab[dc][5][1][0], tabA[da][2][0][0]*tabB[db][1][0])
+        assert tab[dc][0][2][0] == 0.0
+        assert tab[dc][1][2][0] == 0.0
+        assert tab[dc][2][2][0] == 0.0
+        assert tab[dc][3][2][0] == 0.0
+        assert tab[dc][4][2][0] == 0.0
+        assert tab[dc][5][2][0] == 0.0
+
+
+def test_TFE_2Dx1D_vector_quad_hdiv():
+    T = UFCInterval()
+    P1 = Lagrange(T, 1)
+    P0 = DiscontinuousLagrange(T, 0)
+    P1_DG = DiscontinuousLagrange(T, 1)
+
+    P1P0 = Hdiv(TensorProductElement(P1, P0))
+    P0P1 = Hdiv(TensorProductElement(P0, P1))
+    horiz_elt = EnrichedElement(P1P0, P0P1)
+    elt = Hdiv(TensorProductElement(horiz_elt, P1_DG))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tA = P1.tabulate(1, [(0.1,)])
+    tB = P0.tabulate(1, [(0.2,)])
+    tC = P0.tabulate(1, [(0.1,)])
+    tD = P1.tabulate(1, [(0.2,)])
+    tE = P1_DG.tabulate(1, [(0.3,)])
+    for da, db, dc in [[(0,), (0,), (0,)], [(1,), (0,), (0,)], [(0,), (1,), (0,)], [(0,), (0,), (1,)]]:
+        dd = da + db + dc
+        assert np.isclose(tab[dd][0][0][0], -tA[da][0][0]*tB[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][1][0][0], -tA[da][0][0]*tB[db][0][0]*tE[dc][1][0])
+        assert np.isclose(tab[dd][2][0][0], -tA[da][1][0]*tB[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][3][0][0], -tA[da][1][0]*tB[db][0][0]*tE[dc][1][0])
+        assert tab[dd][4][0][0] == 0.0
+        assert tab[dd][5][0][0] == 0.0
+        assert tab[dd][6][0][0] == 0.0
+        assert tab[dd][7][0][0] == 0.0
+        assert tab[dd][0][1][0] == 0.0
+        assert tab[dd][1][1][0] == 0.0
+        assert tab[dd][2][1][0] == 0.0
+        assert tab[dd][3][1][0] == 0.0
+        assert np.isclose(tab[dd][4][1][0], tC[da][0][0]*tD[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][5][1][0], tC[da][0][0]*tD[db][0][0]*tE[dc][1][0])
+        assert np.isclose(tab[dd][6][1][0], tC[da][0][0]*tD[db][1][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][7][1][0], tC[da][0][0]*tD[db][1][0]*tE[dc][1][0])
+        assert tab[dd][0][2][0] == 0.0
+        assert tab[dd][1][2][0] == 0.0
+        assert tab[dd][2][2][0] == 0.0
+        assert tab[dd][3][2][0] == 0.0
+        assert tab[dd][4][2][0] == 0.0
+        assert tab[dd][5][2][0] == 0.0
+        assert tab[dd][6][2][0] == 0.0
+        assert tab[dd][7][2][0] == 0.0
+
+
+def test_TFE_2Dx1D_vector_quad_hcurl():
+    T = UFCInterval()
+    P1 = Lagrange(T, 1)
+    P0 = DiscontinuousLagrange(T, 0)
+
+    P1P0 = Hcurl(TensorProductElement(P1, P0))
+    P0P1 = Hcurl(TensorProductElement(P0, P1))
+    horiz_elt = EnrichedElement(P1P0, P0P1)
+    elt = Hcurl(TensorProductElement(horiz_elt, P1))
+    assert elt.value_shape() == (3,)
+    tab = elt.tabulate(1, [(0.1, 0.2, 0.3)])
+    tA = P1.tabulate(1, [(0.1,)])
+    tB = P0.tabulate(1, [(0.2,)])
+    tC = P0.tabulate(1, [(0.1,)])
+    tD = P1.tabulate(1, [(0.2,)])
+    tE = P1.tabulate(1, [(0.3,)])
+    for da, db, dc in [[(0,), (0,), (0,)], [(1,), (0,), (0,)], [(0,), (1,), (0,)], [(0,), (0,), (1,)]]:
+        dd = da + db + dc
+        assert tab[dd][0][0][0] == 0.0
+        assert tab[dd][1][0][0] == 0.0
+        assert tab[dd][2][0][0] == 0.0
+        assert tab[dd][3][0][0] == 0.0
+        assert np.isclose(tab[dd][4][0][0], tC[da][0][0]*tD[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][5][0][0], tC[da][0][0]*tD[db][0][0]*tE[dc][1][0])
+        assert np.isclose(tab[dd][6][0][0], tC[da][0][0]*tD[db][1][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][7][0][0], tC[da][0][0]*tD[db][1][0]*tE[dc][1][0])
+        assert np.isclose(tab[dd][0][1][0], tA[da][0][0]*tB[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][1][1][0], tA[da][0][0]*tB[db][0][0]*tE[dc][1][0])
+        assert np.isclose(tab[dd][2][1][0], tA[da][1][0]*tB[db][0][0]*tE[dc][0][0])
+        assert np.isclose(tab[dd][3][1][0], tA[da][1][0]*tB[db][0][0]*tE[dc][1][0])
+        assert tab[dd][4][1][0] == 0.0
+        assert tab[dd][5][1][0] == 0.0
+        assert tab[dd][6][1][0] == 0.0
+        assert tab[dd][7][1][0] == 0.0
+        assert tab[dd][0][2][0] == 0.0
+        assert tab[dd][1][2][0] == 0.0
+        assert tab[dd][2][2][0] == 0.0
+        assert tab[dd][3][2][0] == 0.0
+        assert tab[dd][4][2][0] == 0.0
+        assert tab[dd][5][2][0] == 0.0
+        assert tab[dd][6][2][0] == 0.0
+        assert tab[dd][7][2][0] == 0.0
+
+
+if __name__ == '__main__':
+    import os
+    pytest.main(os.path.abspath(__file__))

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



More information about the debian-science-commits mailing list