Repository: mscroggs/symfem

Issue:
HCT element is incorrect
**Describe the bug**
The basis functions should be C1 continuous inside the cell. They are currently not

Comments:

Patch:
diff --git a/++version.py b/++version.py
index 6a1f942..c1a2906 100644
--- a/++version.py
+++ b/++version.py
@@ -20,10 +20,16 @@ with open("VERSION") as f:
 
 now = datetime.now()
 if now.year == version[0] and now.month == version[1]:
-    new_version = (now.year, now.month, version[2] + 1)
+    if len(version) == 2:
+        new_version = (now.year, now.month, 1)
+    else:
+        new_version = (now.year, now.month, version[2] + 1)
 else:
-    new_version = (now.year, now.month, 1)
-new_version_str = ".".join([f"{i}" for i in new_version])
+    new_version = (now.year, now.month, 0)
+if len(new_version) == 2:
+    new_version_str = f"{new_version[0]}.{new_version[1]}"
+else:
+    new_version_str = f"{new_version[0]}.{new_version[1]}.{new_version[2]}"
 
 # VERSION file
 with open("VERSION", "w") as f:
diff --git a/CHANGELOG_SINCE_LAST_VERSION.md b/CHANGELOG_SINCE_LAST_VERSION.md
index e69de29..75d7d2c 100644
--- a/CHANGELOG_SINCE_LAST_VERSION.md
+++ b/CHANGELOG_SINCE_LAST_VERSION.md
@@ -0,0 +1,1 @@
+- Corrected HCT and rHCT elements
diff --git a/symfem/elements/hct.py b/symfem/elements/hct.py
index b184e08..17218b5 100644
--- a/symfem/elements/hct.py
+++ b/symfem/elements/hct.py
@@ -8,14 +8,13 @@ import typing
 
 import sympy
 
-from ..basis_functions import BasisFunction
 from ..finite_element import CiarletElement
 from ..functionals import (DerivativePointEvaluation, ListOfFunctionals, PointEvaluation,
                            PointNormalDerivativeEvaluation)
-from ..functions import FunctionInput, VectorFunction
+from ..functions import FunctionInput, ScalarFunction
 from ..piecewise_functions import PiecewiseFunction
 from ..references import Reference
-from .hermite import Hermite
+from ..symbols import x
 
 
 class HsiehCloughTocher(CiarletElement):
@@ -28,7 +27,6 @@ class HsiehCloughTocher(CiarletElement):
             reference: The reference element
             order: The polynomial order
         """
-        from symfem import create_reference
         assert order == 3
         assert reference.name == "triangle"
         dofs: ListOfFunctionals = []
@@ -48,52 +46,25 @@ class HsiehCloughTocher(CiarletElement):
             (reference.vertices[1], reference.vertices[2], mid),
             (reference.vertices[2], reference.vertices[0], mid)]
 
-        refs = [create_reference("triangle", vs) for vs in subs]
-
-        hermite_spaces = [Hermite(ref, 3) for ref in refs]
-
-        piece_list: typing.List[typing.Tuple[typing.Union[int, BasisFunction], ...]] = []
-        piece_list.append((hermite_spaces[0].get_basis_function(0), 0,
-                           hermite_spaces[2].get_basis_function(3)))
-        piece_list.append((hermite_spaces[0].get_basis_function(1), 0,
-                           hermite_spaces[2].get_basis_function(4)))
-        piece_list.append((hermite_spaces[0].get_basis_function(2), 0,
-                           hermite_spaces[2].get_basis_function(5)))
-        piece_list.append((hermite_spaces[0].get_basis_function(3),
-                           hermite_spaces[1].get_basis_function(0), 0))
-        piece_list.append((hermite_spaces[0].get_basis_function(4),
-                           hermite_spaces[1].get_basis_function(1), 0))
-        piece_list.append((hermite_spaces[0].get_basis_function(5),
-                           hermite_spaces[1].get_basis_function(2), 0))
-        piece_list.append((hermite_spaces[0].get_basis_function(6),
-                           hermite_spaces[1].get_basis_function(6),
-                           hermite_spaces[2].get_basis_function(6)))
-        piece_list.append((hermite_spaces[0].get_basis_function(7),
-                           hermite_spaces[1].get_basis_function(7),
-                           hermite_spaces[2].get_basis_function(7)))
-        piece_list.append((hermite_spaces[0].get_basis_function(8),
-                           hermite_spaces[1].get_basis_function(8),
-                           hermite_spaces[2].get_basis_function(8)))
-        piece_list.append((0, hermite_spaces[1].get_basis_function(3),
-                           hermite_spaces[2].get_basis_function(0)))
-        piece_list.append((0, hermite_spaces[1].get_basis_function(4),
-                           hermite_spaces[2].get_basis_function(1)))
-        piece_list.append((0, hermite_spaces[1].get_basis_function(5),
-                           hermite_spaces[2].get_basis_function(2)))
-
-        # TODO: are these right to remove??
-        # piece_list.append((hermite_spaces[0].get_basis_function(9), 0, 0))
-        # piece_list.append((0, hermite_spaces[1].get_basis_function(9), 0))
-        # piece_list.append((0, 0, hermite_spaces[2].get_basis_function(9)))
-
-        piece_list2: typing.List[VectorFunction] = []
-        for i in piece_list:
-            piece_list2.append(VectorFunction(i))
+        piece_list = [tuple(ScalarFunction(p) for _ in range(3))
+                      for p in [1, x[0], x[1], x[0]**2, x[0]*x[1], x[1]**2,
+                                x[0]**3, x[0]**2*x[1], x[0]*x[1]**2, x[1]**3]]
+        piece_list.append((
+            ScalarFunction(-23*x[0]**3 + 24*x[0]**2*x[1] - 12*x[0]*x[1]**2 + 36*x[1]**2),
+            ScalarFunction(
+                -28*x[0]**3 + 12*x[0]**2*x[1] + 9*x[0]**2 - 3*x[0] + 32*x[1]**3 + 12*x[1] - 1),
+            ScalarFunction(-15*x[0]**2 - 33*x[0]*x[1]**2 + 30*x[0]*x[1] + 22*x[1]**3 + 21*x[1]**2)))
+        piece_list.append((
+            ScalarFunction(
+                22*x[0]**3 - 21*x[0]**2*x[1] - 12*x[0]*x[1]**2 + 30*x[0]*x[1] - 24*x[1]**2),
+            ScalarFunction(
+                32*x[0]**3 + 12*x[0]**2*x[1] - 21*x[0]**2 + 12*x[0] - 28*x[1]**3 - 3*x[1] - 1),
+            ScalarFunction(15*x[0]**2 + 12*x[0]*x[1]**2 - 23*x[1]**3 - 9*x[1]**2)))
 
         poly: typing.List[FunctionInput] = []
         poly += [
             PiecewiseFunction({i: j for i, j in zip(subs, p)}, 2)
-            for p in piece_list2]
+            for p in piece_list]
 
         super().__init__(
             reference, order, poly, dofs, reference.tdim, 1
diff --git a/symfem/elements/rhct.py b/symfem/elements/rhct.py
index 395cd71..bddbfa3 100644
--- a/symfem/elements/rhct.py
+++ b/symfem/elements/rhct.py
@@ -10,47 +10,12 @@ import sympy
 
 from ..finite_element import CiarletElement
 from ..functionals import DerivativePointEvaluation, ListOfFunctionals, PointEvaluation
-from ..functions import FunctionInput
+from ..functions import FunctionInput, ScalarFunction
 from ..piecewise_functions import PiecewiseFunction
 from ..references import Reference
 from ..symbols import x
 
 
-class P1Hermite(CiarletElement):
-    """P1Hermite finite element."""
-
-    def __init__(self, reference: Reference, order: int, poly: typing.List[FunctionInput]):
-        """Create the element.
-
-        Args:
-            reference: The reference element
-            order: The polynomial order
-            poly: The polynomial basis
-        """
-        assert order == 3
-        dofs: ListOfFunctionals = []
-        for v_n, vs in enumerate(reference.vertices):
-            dofs.append(PointEvaluation(reference, vs, entity=(0, v_n)))
-            dofs.append(DerivativePointEvaluation(reference, vs, (1, 0), entity=(0, v_n)))
-            dofs.append(DerivativePointEvaluation(reference, vs, (0, 1), entity=(0, v_n)))
-
-        super().__init__(reference, order, poly, dofs, reference.tdim, 1)
-
-    def init_kwargs(self) -> typing.Dict[str, typing.Any]:
-        """Return the kwargs used to create this element.
-
-        Returns:
-            Keyword argument dictionary
-        """
-        return {"poly": self._basis}
-
-    names: typing.List[str] = []
-    references = ["triangle"]
-    min_order = 3
-    max_order = 3
-    continuity = "C0"
-
-
 class ReducedHsiehCloughTocher(CiarletElement):
     """Reduced Hsieh-Clough-Tocher finite element."""
 
@@ -61,7 +26,6 @@ class ReducedHsiehCloughTocher(CiarletElement):
             reference: The reference element
             order: The polynomial order
         """
-        from symfem import create_reference
         assert order == 3
         assert reference.name == "triangle"
         dofs: ListOfFunctionals = []
@@ -77,48 +41,24 @@ class ReducedHsiehCloughTocher(CiarletElement):
             (reference.vertices[1], reference.vertices[2], mid),
             (reference.vertices[2], reference.vertices[0], mid)]
 
-        refs = [create_reference("triangle", vs) for vs in subs]
-
-        polys: typing.List[typing.List[FunctionInput]] = [[], [], []]
-        for i in range(order + 1):
-            for j in range(order + 1 - i):
-                if i != 2 or j != 1:
-                    polys[0].append(x[0] ** i * x[1] ** j)
-        polys[1] += [1, x[0], x[0] ** 2, x[1], x[0] * x[1], x[1] ** 2,
-                     x[0] * x[1] ** 2 - x[0] ** 2 * x[1],
-                     x[0] ** 3 - x[1] ** 3, x[0] ** 3 + 3 * x[0] * x[1] ** 2]
-        for i in range(order + 1):
-            for j in range(order + 1 - i):
-                if i != 1 or j != 2:
-                    polys[2].append(x[0] ** i * x[1] ** j)
-
-        bases = []
-        for r, p in zip(refs, polys):
-            bf = []
-            for f in P1Hermite(r, 3, p).get_basis_functions():
-                bf.append(f)
-            bases.append(bf)
-
-        piece_list: typing.List[typing.Tuple[FunctionInput, ...]] = []
-        piece_list.append((bases[0][0], 0, bases[2][3]))
-        piece_list.append((bases[0][1], 0, bases[2][4]))
-        piece_list.append((bases[0][2], 0, bases[2][5]))
-        piece_list.append((bases[0][3], bases[1][0], 0))
-        piece_list.append((bases[0][4], bases[1][1], 0))
-        piece_list.append((bases[0][5], bases[1][2], 0))
-        # TODO: are these right to remove??
-        # piece_list.append((bases[0][6], bases[1][6], bases[2][6]))
-        # piece_list.append((bases[0][7], bases[1][7], bases[2][7]))
-        # piece_list.append((bases[0][8], bases[1][8], bases[2][8]))
-        piece_list.append((0, bases[1][3], bases[2][0]))
-        piece_list.append((0, bases[1][4], bases[2][1]))
-        piece_list.append((0, bases[1][5], bases[2][2]))
+        piece_list = [tuple(ScalarFunction(p) for _ in range(3))
+                      for p in [1, x[0], x[1], x[0]**2, x[0]*x[1], x[1]**2,
+                                x[0]**3 - x[1]**3]]
+        piece_list.append((
+            ScalarFunction(4*x[0]**3 - 3*x[0]*x[1]**2 + 2*x[0]*x[1] + 4*x[1]**2),
+            ScalarFunction(7*x[0]**3 + 12*x[0]**2*x[1] - 7*x[0]**2 + 9*x[0]*x[1]**2
+                           - 14*x[0]*x[1] + 5*x[0] + 4*x[1] - 1),
+            ScalarFunction(3*x[0]**3 + x[0]**2 - 2*x[1]**3 + 5*x[1]**2)))
+        piece_list.append((
+            ScalarFunction(25*x[0]**3 - 24*x[0]*x[1]**2 + 30*x[0]*x[1] - 24*x[1]**2),
+            ScalarFunction(35*x[0]**3 + 33*x[0]**2*x[1] - 21*x[0]**2 - 12*x[0]*x[1]**2
+                           + 12*x[0] - 28*x[1]**3 - 3*x[1] - 1),
+            ScalarFunction(3*x[0]**3 + 21*x[0]**2*x[1] + 15*x[0]**2 - 23*x[1]**3 - 9*x[1]**2)))
 
         poly: typing.List[FunctionInput] = []
         poly += [
             PiecewiseFunction({i: j for i, j in zip(subs, p)}, 2)
-            for p in piece_list
-        ]
+            for p in piece_list]
 
         super().__init__(
             reference, order, poly, dofs, reference.tdim, 1
diff --git a/symfem/elements/vector_enriched_galerkin.py b/symfem/elements/vector_enriched_galerkin.py
index d7f5bf7..0bf3110 100644
--- a/symfem/elements/vector_enriched_galerkin.py
+++ b/symfem/elements/vector_enriched_galerkin.py
@@ -32,7 +32,7 @@ class Enrichment(CiarletElement):
 
         super().__init__(reference, 1, poly, dofs, reference.tdim, reference.tdim)
 
-    names = []
+    names: typing.List[str] = []
     references = ["triangle", "quadrilateral", "tetrahedron", "hexahedron"]
     min_order = 1
     max_order = 1


Test patch:
diff --git a/test/test_hct.py b/test/test_hct.py
index eeadde8..58009b5 100644
--- a/test/test_hct.py
+++ b/test/test_hct.py
@@ -1,80 +1,69 @@
 """Test Hsieh-Clough-Tocher elements."""
 
+import pytest
 import sympy
 
 import symfem
+from symfem.functions import ScalarFunction
 from symfem.symbols import t, x
 from symfem.utils import allequal
 
 half = sympy.Rational(1, 2)
 
 
-def test_hct():
-    e = symfem.create_element("triangle", "HCT", 3)
+@pytest.mark.parametrize("family", ["HCT", "rHCT"])
+def test_c1_continuity(family):
+    e = symfem.create_element("triangle", family, 3)
     for f in e.get_polynomial_basis():
         # edge from (1,0) to (1/3,1/3)
         f1 = f.get_piece((half, 0))
         f2 = f.get_piece((half, half))
+        grad_f1 = f1.grad(2)
+        grad_f2 = f2.grad(2)
         line = ((1 - 2 * t[0], t[0]))
         f1 = f1.subs(x[:2], line)
         f2 = f2.subs(x[:2], line)
+        grad_f1 = grad_f1.subs(x[:2], line)
+        grad_f2 = grad_f2.subs(x[:2], line)
         assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
+        assert allequal(grad_f1, grad_f2)
 
         # edge from (0,1) to (1/3,1/3)
         f1 = f.get_piece((half, half))
         f2 = f.get_piece((0, half))
+        grad_f1 = f1.grad(2)
+        grad_f2 = f2.grad(2)
         line = ((t[0], 1 - 2 * t[0]))
         f1 = f1.subs(x[:2], line)
         f2 = f2.subs(x[:2], line)
+        grad_f1 = grad_f1.subs(x[:2], line)
+        grad_f2 = grad_f2.subs(x[:2], line)
         assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
+        assert allequal(grad_f1, grad_f2)
 
         # edge from (0,0) to (1/3,1/3)
         f1 = f.get_piece((0, half))
         f2 = f.get_piece((half, 0))
+        grad_f1 = f1.grad(2)
+        grad_f2 = f2.grad(2)
         line = ((t[0], t[0]))
         f1 = f1.subs(x[:2], line)
         f2 = f2.subs(x[:2], line)
+        grad_f1 = grad_f1.subs(x[:2], line)
+        grad_f2 = grad_f2.subs(x[:2], line)
         assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
+        assert allequal(grad_f1, grad_f2)
 
 
-def test_rhct():
+def test_rcht_linear_normal_derivatices():
     e = symfem.create_element("triangle", "rHCT", 3)
     for f in e.get_polynomial_basis():
-        # edge from (1,0) to (1/3,1/3)
-        f1 = f.get_piece((half, 0))
-        f2 = f.get_piece((half, half))
-        line = ((1 - 2 * t[0], t[0]))
-        f1 = f1.subs(x[:2], line)
-        f2 = f2.subs(x[:2], line)
-        assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
-
-        # edge from (0,1) to (1/3,1/3)
-        f1 = f.get_piece((half, half))
-        f2 = f.get_piece((0, half))
-        line = ((t[0], 1 - 2 * t[0]))
-        f1 = f1.subs(x[:2], line)
-        f2 = f2.subs(x[:2], line)
-        assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
-
-        # edge from (0,0) to (1/3,1/3)
-        f1 = f.get_piece((0, half))
-        f2 = f.get_piece((half, 0))
-        line = ((t[0], t[0]))
-        f1 = f1.subs(x[:2], line)
-        f2 = f2.subs(x[:2], line)
-        assert allequal(f1, f2)
-        assert allequal(f1.grad(2), f2.grad(2))
-
         # Check that normal derivatives are linear
         f1 = f.get_piece((half, 0)).diff(x[1]).subs(x[1], 0)
         f2 = f.get_piece((half, half))
         f2 = (f2.diff(x[0]) + f2.diff(x[1])).subs(x[1], 1 - x[0])
         f3 = f.get_piece((0, half)).diff(x[0]).subs(x[0], 0)
+        print(f)
         assert f1.diff(x[0]).diff(x[0]) == 0
         assert f2.diff(x[0]).diff(x[0]) == 0
         assert f3.diff(x[1]).diff(x[1]) == 0
@@ -85,13 +74,14 @@ def test_rhct_integral():
     ref = element.reference
     f1 = element.get_basis_function(1).directional_derivative((1, 0))
     f2 = element.get_basis_function(6).directional_derivative((1, 0))
-    integrand = f1 * f2
 
+    integrand = f1 * f2
     third = sympy.Rational(1, 3)
-    expr = (f1*f2).pieces[((0, 1), (0, 0), (third, third))].as_sympy()
-    assert len((f1*f2).pieces) == 3
-    assert (f1*f2).pieces[((0, 0), (1, 0), (third, third))] == 0
-    assert (f1*f2).pieces[((1, 0), (0, 1), (third, third))] == 0
+    integrand.pieces[((0, 0), (1, 0), (third, third))] = ScalarFunction(0)
+    integrand.pieces[((1, 0), (0, 1), (third, third))] = ScalarFunction(0)
+
+    expr = integrand.pieces[((0, 1), (0, 0), (third, third))].as_sympy()
+    assert len(integrand.pieces) == 3
 
     assert sympy.integrate(sympy.integrate(
         expr, (x[1], x[0], 1 - 2 * x[0])), (x[0], 0, third)) == integrand.integral(ref, x)