Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
c5d916d
add mcoked example for orientations
indiamai Sep 29, 2025
fe85a47
cg working - draft rt test
indiamai Oct 3, 2025
9aa1149
add reversed matrices transforms
indiamai Oct 6, 2025
70c85a0
oriented mat perms, nedelec 2nd order
indiamai Oct 10, 2025
1d40894
tidy up tests
indiamai Oct 14, 2025
3ef2010
tidy up mat perms
indiamai Oct 16, 2025
a93e1f4
Fix 2nd order nedelec (#41)
indiamai Jan 29, 2026
ba44b04
Merge branch 'main' into indiamai/test_orientations_elem
indiamai Jan 29, 2026
3c0363c
punt on orientations - how was this so easy
indiamai Jan 29, 2026
c6de1a2
CG3 on tets working
indiamai Feb 3, 2026
45861c3
nedelec 1 on tets works
indiamai Feb 4, 2026
3330602
testing
indiamai Feb 5, 2026
73b605b
it seems RT on tets may be working
indiamai Feb 11, 2026
cd34ead
i think cg4 defined by fiat works
indiamai Feb 18, 2026
693e374
under very precise conditions cg4 works
indiamai Feb 19, 2026
9ccf3aa
tidy up, make real test
indiamai Feb 19, 2026
36f08b3
fix branch
indiamai Feb 19, 2026
109dfb0
try pip installing firedrake and tidy
indiamai Feb 19, 2026
ba8526a
try changing docker image
indiamai Feb 19, 2026
80d172b
try changing docker image
indiamai Feb 19, 2026
70e8951
experiment with petsctools
indiamai Feb 19, 2026
a70a981
try purging and specifically install petsc4py
indiamai Feb 19, 2026
e06063f
fix directory
indiamai Feb 19, 2026
e4bd65f
remove added petsctools
indiamai Feb 19, 2026
ec7756e
try installing specific version of petsctools
indiamai Feb 19, 2026
32d82c4
try installing specific version of petsctools - better url
indiamai Feb 19, 2026
e7da800
uninstall + python -m everywhere
indiamai Feb 19, 2026
9fc414f
remove uninstall
indiamai Feb 19, 2026
eb96b9a
fix syntax issue
indiamai Feb 19, 2026
6edc919
try installing fresh firedrake
indiamai Feb 19, 2026
c9b9dd9
fix syntax
indiamai Feb 19, 2026
297d543
add directory back in
indiamai Feb 19, 2026
97673f3
add directory back in... again
indiamai Feb 19, 2026
2e9c9f3
try building everything from scratch
indiamai Feb 19, 2026
cc0c9f1
more fixing
indiamai Feb 19, 2026
ed97883
more fixing
indiamai Feb 19, 2026
4f5ce53
steal firedrake workflow
indiamai Feb 23, 2026
dcc2e12
incorrect attribute
indiamai Feb 23, 2026
803c19e
change petsc path
indiamai Feb 23, 2026
3d60b85
try different firedrake branch
indiamai Feb 23, 2026
82c94f9
try different firedrake branch
indiamai Feb 23, 2026
0a7c56b
try different firedrake branch
indiamai Feb 23, 2026
cb7e952
finalise sub ents change
indiamai Feb 23, 2026
e312e84
try installing one branch then using another
indiamai Feb 23, 2026
c9cf9e0
try not building sdist to get editable
indiamai Feb 24, 2026
f0a27b1
fix directory
indiamai Feb 24, 2026
6123d48
cd into fuse before making test
indiamai Feb 24, 2026
ff901a6
run make
indiamai Feb 24, 2026
45028d3
Fix tests now i have ci
indiamai Feb 24, 2026
1de0038
clean up tests, xfail ned2
indiamai Feb 25, 2026
9d2451c
fix final test
indiamai Feb 25, 2026
87262ee
redo non trivial transform groups
indiamai Feb 26, 2026
35dc8a1
try to run ci using split tests
indiamai Mar 2, 2026
2410d17
fix path
indiamai Mar 2, 2026
f785def
maybe nd2 on tets
indiamai Mar 2, 2026
86ab83b
use different group for cg4
indiamai Mar 3, 2026
fc0bbdd
fix up RT
indiamai Mar 3, 2026
f1a21b8
lint
indiamai Mar 3, 2026
9c2b5b8
...it might be working
indiamai Mar 4, 2026
9bf3a1b
switch group for cg4 back over
indiamai Mar 4, 2026
3a1f6fc
some tidying
indiamai Mar 4, 2026
6ae8526
xfail test
indiamai Mar 5, 2026
c12608e
fix quadrature degree, implement bdm and ned 2nd kind on triangles
indiamai Mar 5, 2026
16a67c2
remember to add test files
indiamai Mar 5, 2026
76a650b
lint
indiamai Mar 5, 2026
9593bce
tet BDM and ned 2nd kind
indiamai Mar 5, 2026
a9c3dd6
lint
indiamai Mar 5, 2026
5072051
enable polynomial kernel to be vector valued, bdm2 on triangles
indiamai Mar 6, 2026
a54346c
remove breakpoint and lint
indiamai Mar 6, 2026
160d166
draft nedelec 3 on tets, refactor some code in poly kernel
indiamai Mar 6, 2026
e7d4fb0
compute basis group, fix inconsistency in group application, tidy
indiamai Mar 9, 2026
a87e1b9
Merge branch 'main' into indiamai/complex_orientations
indiamai Mar 9, 2026
ec1e3dd
first stab at using cosets in orientations
indiamai Mar 9, 2026
2c96175
fiddling with nd3 no progress
indiamai Mar 10, 2026
80304b7
add equality to cells
indiamai Apr 1, 2025
d881c4a
refactor
indiamai Apr 1, 2025
e49c703
first steps to making a fuse element from a flattened tensor prod
indiamai Apr 1, 2025
2fb6b88
first level of generated hasse diagram
indiamai Apr 3, 2025
e6341b9
fix recursion issue
indiamai Apr 7, 2025
208d64a
lint
indiamai Apr 7, 2025
3f06f03
refactor work on poly spaces and trying to figure out how to make qua…
indiamai Apr 7, 2025
01ac541
fix contains in polynomial sets
indiamai Apr 10, 2025
78e0861
working on cell
indiamai Apr 14, 2025
e9560f9
Ensure all numeric reps are 0..n over the whole group
indiamai Apr 23, 2025
37b6124
comparision to existing
indiamai Apr 24, 2025
d2d2d4b
tidy up
indiamai Mar 10, 2026
06a50d0
lint
indiamai Mar 10, 2026
52fa438
small fixes
indiamai Mar 10, 2026
0e197b4
modifications to polynomial kernel to take into account value shape
indiamai Mar 11, 2026
87dfe97
fix mistake in components
indiamai Mar 11, 2026
ea8265b
add jacobian
indiamai Mar 11, 2026
6ad49d3
ensure vector kernel and scalar poly kernel behave the same
indiamai Mar 11, 2026
3eaf4c7
bug fix
indiamai Mar 11, 2026
fdf00cc
use ufl sobolev spaces
indiamai Mar 11, 2026
f7f2709
first stab at addition
indiamai Mar 11, 2026
860f11e
fix bubble - addition working
indiamai Mar 12, 2026
d196320
fix tests
indiamai Mar 12, 2026
2458baf
Merge branch 'indiamai/addition' into indiamai/complex_orientations
indiamai Mar 16, 2026
3bad4ca
merge stash
indiamai Mar 16, 2026
7b2a0cc
add barycentric polynomials
indiamai Mar 16, 2026
61785ff
immerse polynomial vectors
indiamai Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ tests:
@echo " Running all tests"
@FIREDRAKE_USE_FUSE=1 python3 -m coverage run -p -m pytest -rx test

mini_tests:
@FIREDRAKE_USE_FUSE=1 python3 -m pytest test/test_2d_examples_docs.py
@FIREDRAKE_USE_FUSE=1 python3 -m pytest test/test_convert_to_fiat.py::test_1d
@FIREDRAKE_USE_FUSE=1 python3 -m pytest test/test_orientations.py::test_surface_vec_rt
@FIREDRAKE_USE_FUSE=1 python3 -m pytest test/test_convert_to_fiat.py::test_projection_convergence_3d\[construct_tet_ned-N1curl-1-0.8\]

coverage:
@python3 -m coverage combine
@python3 -m coverage report -m
Expand Down
5 changes: 3 additions & 2 deletions fuse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fuse.cells import Point, Edge, polygon, make_tetrahedron, constructCellComplex

from fuse.cells import Point, Edge, polygon, line, make_tetrahedron, constructCellComplex, TensorProductPoint
from fuse.groups import S1, S2, S3, D4, Z3, Z4, C3, C4, S4, A4, diff_C3, tet_edges, tet_faces, sq_edges, GroupRepresentation, PermutationSetRepresentation, get_cyc_group, get_sym_group
from fuse.dof import DeltaPairing, DOF, L2Pairing, FuseFunction, PointKernel, VectorKernel, PolynomialKernel, ComponentKernel
from fuse.dof import DeltaPairing, DOF, L2Pairing, FuseFunction, PointKernel, VectorKernel, BarycentricPolynomialKernel, PolynomialKernel, ComponentKernel
from fuse.triples import ElementTriple, DOFGenerator, immerse
from fuse.traces import TrH1, TrGrad, TrHess, TrHCurl, TrHDiv
from fuse.tensor_products import tensor_product
Expand Down
181 changes: 160 additions & 21 deletions fuse/cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ def compute_scaled_verts(d, n):
raise ValueError("Dimension {} not supported".format(d))


def line():
"""
Constructs the default 1D interval
"""
return Point(1, [Point(0), Point(0)], vertex_num=2)


def polygon(n):
"""
Constructs the 2D default cell with n sides/vertices
Expand Down Expand Up @@ -315,8 +322,8 @@ def __init__(self, d, edges=[], vertex_num=None, oriented=False, group=None, edg
self.group = group
if not group:
self.group = self.compute_cell_group()

self.group = self.group.add_cell(self)
self.basis_group = self.compute_basis_group()

def compute_attachments(self, n, points, orientations=None):
"""
Expand Down Expand Up @@ -373,6 +380,7 @@ def compute_cell_group(self):
"""
verts = self.ordered_vertices()
v_coords = [self.get_node(v, return_coords=True) for v in verts]

n = len(verts)
max_group = SymmetricGroup(n)
edges = [edge.ordered_vertices() for edge in self.edges()]
Expand All @@ -388,6 +396,19 @@ def compute_cell_group(self):
break
return fuse_groups.PermutationSetRepresentation(list(accepted_perms))

def compute_basis_group(self):
if self.dimension == 0:
return fuse_groups.S1.add_cell(self)
bvs = self.basis_vectors()
bv_0 = bvs[0]
basis_group_members = []
for bv in bvs:
for g in self.group.members():
if np.allclose(np.array(g(bv_0)), np.array(bv)):
basis_group_members += [g.perm]
break
return fuse_groups.PermutationSetRepresentation(list(basis_group_members)).add_cell(self)

def get_spatial_dimension(self):
return self.dimension

Expand Down Expand Up @@ -475,12 +496,8 @@ def _subentity_traversal(self, sub_ents, min_ids):
sub_ents = self.get_node(p)._subentity_traversal(sub_ents, min_ids)
if dim > 1:
connections = [(c.point.id, c.point.group.identity) for c in self.connections]
# if self.oriented:
# connections = self.permute_entities(self.oriented, dim - 1)
# if self.dimension == 2:
# connections = [connections[-1]] + connections[:-1]
# print([self.get_node(c[0]).id - min_ids[1] for c in connections])
# print([c.point.id - min_ids[1] for c in self.connections])
for e, o in connections:
p = self.get_node(e).orient(o)
p_dim = p.get_spatial_dimension()
Expand All @@ -502,6 +519,15 @@ def get_starter_ids(self):
min_ids = [min(dimension) for dimension in structure]
return min_ids

def local_id(self, node):
structure = [sorted(generation) for generation in nx.topological_generations(self.G)]
structure.reverse()
min_id = self.get_starter_ids()
for d in range(len(structure)):
if node.id in structure[d]:
return node.id - min_id[d]
raise ValueError("Node not found in cell")

def graph_dim(self):
if self.oriented:
dim = self.dimension + 1
Expand Down Expand Up @@ -544,6 +570,9 @@ def ordered_vertices(self, get_class=False):
return self.oriented.permute(verts)
return verts

def ordered_vertex_coords(self):
return [self.get_node(o, return_coords=True) for o in self.ordered_vertices()]

def d_entities_ids(self, d):
return self.d_entities(d, get_class=False)

Expand Down Expand Up @@ -639,13 +668,12 @@ def permute_entities(self, g, d):

return reordered_entities

def basis_vectors(self, return_coords=True, entity=None, order=False):
def basis_vectors(self, return_coords=True, entity=None, order=False, norm=True):
if not entity:
entity = self
self_levels = [generation for generation in nx.topological_generations(self.G)]
vertices = entity.ordered_vertices()
if self.dimension == 0:
# return [[]
raise ValueError("Dimension 0 entities cannot have Basis Vectors")
if self.oriented:
# ordered_vertices() handles the orientation so we want to drop the orientation node
Expand All @@ -659,7 +687,10 @@ def basis_vectors(self, return_coords=True, entity=None, order=False):
for v in vertices[1:]:
if return_coords:
v_coords = self.attachment(top_level_node, v)()
sub = normalise(np.subtract(v_coords, v_0_coords))
if norm:
sub = normalise(np.subtract(v_coords, v_0_coords))
else:
sub = np.subtract(v_coords, v_0_coords)
if not hasattr(sub, "__iter__"):
basis_vecs.append((sub,))
else:
Expand Down Expand Up @@ -816,12 +847,35 @@ def attachment(self, source, dst):

return lambda *x: fold_reduce(attachments[0], *x)

def attachment_J_det(self, source, dst):
attachment = self.attachment(source, dst)
symbol_names = ["x", "y", "z"]
symbols = []
if self.dim_of_node(dst) == 0:
return 1
for i in range(self.dim_of_node(dst)):
symbols += [sp.Symbol(symbol_names[i])]
J = sp.Matrix(attachment(*symbols)).jacobian(sp.Matrix(symbols))
return np.sqrt(abs(float(sp.det(J.T * J))))

def quadrature(self, degree):
fiat_el = self.to_fiat()
Q = create_quadrature(fiat_el, degree)
pts, wts = Q.get_points(), Q.get_weights()
return pts, wts

def cartesian_to_barycentric(self, pts):
verts = np.array(self.ordered_vertex_coords())
v_0 = self.ordered_vertex_coords()[0]
bvs = np.array(self.basis_vectors(norm=False))
bary_coords = []
for pt in pts:
res = np.matmul(np.linalg.inv(bvs.T), np.array(pt - v_0))
assert np.allclose(sum(bvs[i]*res[i] for i in range(len(bvs))), np.array(pt - v_0))
bary_coords += [(1 - sum(res),) + tuple(res[i] for i in range(len(res)))]
assert np.allclose(np.array(sum(bary_coords[-1][i]*verts[i] for i in range(len(verts)))), pt)
return bary_coords

def cell_attachment(self, dst):
if not isinstance(dst, int):
raise ValueError
Expand Down Expand Up @@ -874,6 +928,13 @@ def dict_id(self):
def _from_dict(o_dict):
return Point(o_dict["dim"], o_dict["edges"], oriented=o_dict["oriented"], cell_id=o_dict["id"])

def equivalent(self, other):
if self.dimension != other.dimension:
return False
if set(self.ordered_vertex_coords()) != set(other.ordered_vertex_coords()):
return False
return self.get_topology() == other.get_topology()


class Edge():
"""
Expand All @@ -897,7 +958,12 @@ def __call__(self, *x):
if hasattr(self.attachment, '__iter__'):
res = []
for attach_comp in self.attachment:
res.append(sympy_to_numpy(attach_comp, syms, x))
if len(attach_comp.atoms(sp.Symbol)) <= len(x):
res.append(sympy_to_numpy(attach_comp, syms, x))
else:
res_val = attach_comp.subs({syms[i]: x[i] for i in range(len(x))})
res.append(res_val)

return tuple(res)
return sympy_to_numpy(self.attachment, syms, x)
return x
Expand Down Expand Up @@ -929,11 +995,11 @@ def _from_dict(o_dict):

class TensorProductPoint():

def __init__(self, A, B, flat=False):
def __init__(self, A, B):
self.A = A
self.B = B
self.dimension = self.A.dimension + self.B.dimension
self.flat = flat
self.flat = False

def ordered_vertices(self):
return self.A.ordered_vertices() + self.B.ordered_vertices()
Expand All @@ -945,8 +1011,8 @@ def get_sub_entities(self):
self.A.get_sub_entities()
self.B.get_sub_entities()

def dimension(self):
return tuple(self.A.dimension, self.B.dimension)
def dim(self):
return (self.A.dimension, self.B.dimension)

def d_entities(self, d, get_class=True):
return self.A.d_entities(d, get_class) + self.B.d_entities(d, get_class)
Expand All @@ -961,17 +1027,91 @@ def vertices(self, get_class=True, return_coords=False):
return verts

def to_ufl(self, name=None):
if self.flat:
return CellComplexToUFL(self, "quadrilateral")
return TensorProductCell(self.A.to_ufl(), self.B.to_ufl())

def to_fiat(self, name=None):
if self.flat:
return CellComplexToFiatHypercube(self, CellComplexToFiatTensorProduct(self, name))
return CellComplexToFiatTensorProduct(self, name)

def flatten(self):
return TensorProductPoint(self.A, self.B, True)
assert self.A.equivalent(self.B)
return FlattenedPoint(self.A, self.B)


class FlattenedPoint(Point, TensorProductPoint):

def __init__(self, A, B):
self.A = A
self.B = B
self.dimension = self.A.dimension + self.B.dimension
self.flat = True
fuse_edges = self.construct_fuse_rep()
super().__init__(self.dimension, fuse_edges)

def to_ufl(self, name=None):
return CellComplexToUFL(self, "quadrilateral")

def to_fiat(self, name=None):
# TODO this should check if it actually is a hypercube
fiat = CellComplexToFiatHypercube(self, CellComplexToFiatTensorProduct(self, name))
return fiat

def construct_fuse_rep(self):
sub_cells = [self.A, self.B]
dims = (self.A.dimension, self.B.dimension)

points = {cell: {i: [] for i in range(max(dims) + 1)} for cell in sub_cells}
attachments = {cell: {i: [] for i in range(max(dims) + 1)} for cell in sub_cells}

for d in range(max(dims) + 1):
for cell in sub_cells:
if d <= cell.dimension:
sub_ent = cell.d_entities(d, get_class=True)
points[cell][d].extend(sub_ent)
for s in sub_ent:
attachments[cell][d].extend(s.connections)

prod_points = list(itertools.product(*[points[cell][0] for cell in sub_cells]))
# temp = prod_points[1]
# prod_points[1] = prod_points[2]
# prod_points[2] = temp
point_cls = [Point(0) for i in range(len(prod_points))]
edges = []

# generate edges of tensor product result
for a in prod_points:
for b in prod_points:
# of all combinations of point, take those where at least one changes and at least one is the same
if any(a[i] == b[i] for i in range(len(a))) and any(a[i] != b[i] for i in range(len(sub_cells))):
# ensure if they change, that edge exists in the existing topology
if all([a[i] == b[i] or (sub_cells[i].local_id(a[i]), sub_cells[i].local_id(b[i])) in list(sub_cells[i]._topology[1].values()) for i in range(len(sub_cells))]):
edges.append((a, b))
# hasse level 1
edge_cls1 = {e: None for e in edges}
for i in range(len(sub_cells)):
for (a, b) in edges:
a_idx = prod_points.index(a)
b_idx = prod_points.index(b)
if a[i] != b[i]:
a_edge = [att for att in attachments[sub_cells[i]][1] if att.point == a[i]][0]
b_edge = [att for att in attachments[sub_cells[i]][1] if att.point == b[i]][0]
edge_cls1[(a, b)] = Point(1, [Edge(point_cls[a_idx], a_edge.attachment, a_edge.o),
Edge(point_cls[b_idx], b_edge.attachment, b_edge.o)])
edge_cls2 = []
# hasse level 2
for i in range(len(sub_cells)):
for (a, b) in edges:
if a[i] == b[i]:
x = sp.Symbol("x")
a_edge = [att for att in attachments[sub_cells[i]][1] if att.point == a[i]][0]
if i == 0:
attach = (x,) + a_edge.attachment
else:
attach = a_edge.attachment + (x,)
edge_cls2.append(Edge(edge_cls1[(a, b)], attach, a_edge.o))
return edge_cls2

def flatten(self):
return self


class CellComplexToFiatSimplex(Simplex):
Expand Down Expand Up @@ -1161,9 +1301,8 @@ def constructCellComplex(name):
return polygon(3).to_ufl(name)
# return ufc_triangle().to_ufl(name)
elif name == "quadrilateral":
interval = Point(1, [Point(0), Point(0)], vertex_num=2)
return TensorProductPoint(interval, interval).flatten().to_ufl(name)
# return ufc_quad().to_ufl(name)
return TensorProductPoint(line(), line()).flatten().to_ufl(name)
# return firedrake_quad().to_ufl(name)
# return polygon(4).to_ufl(name)
elif name == "tetrahedron":
# return ufc_tetrahedron().to_ufl(name)
Expand Down
Loading
Loading