Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
40a00be
add volume scaling
indiamai May 5, 2026
8745746
pin ubuntu version for ci
indiamai May 6, 2026
3b01f8c
fix method issues
indiamai May 6, 2026
a50cbcd
add ordered vertex coords to tensor prod point
indiamai Jun 25, 2026
efa23dc
Merge branch 'indiamai/integral_scaling' into indiamai/full_symmetry
indiamai Jun 25, 2026
91bcf9a
all tensor products of more than two factors
indiamai Jun 29, 2026
d71ac42
refactor fuse rep creation for flattened
indiamai Jun 30, 2026
0228649
forgot some files
indiamai Jun 30, 2026
1b15e7c
change ubuntu to latest
indiamai Jun 30, 2026
27a3338
Test loaded elements function correctly (#63)
indiamai Jun 30, 2026
5a7b2c6
make enriched use entity_ids not dofs
indiamai Jul 1, 2026
9ee0099
change silly merge error in dofs
indiamai Jul 1, 2026
cea9768
hdiv on flattened
indiamai Jul 1, 2026
17e9a77
lint and fix a few random tests
indiamai Jul 1, 2026
eb8e36b
fixing some tests
indiamai Jul 1, 2026
cdce838
fiddling with tests
indiamai Jul 1, 2026
13343be
rename entity_ids
indiamai Jul 2, 2026
c0be44f
Add ufl to ci
indiamai Jul 2, 2026
132580f
small test changes
indiamai Jul 2, 2026
d257834
lint
indiamai Jul 2, 2026
fe1fc20
resolve some issues with tensor prods of tensor prods
indiamai Jul 7, 2026
e4117a4
fix test errors
indiamai Jul 7, 2026
1620363
use refactored pack
indiamai Jul 9, 2026
099f4ec
fixup hcurl transformer
indiamai Jul 9, 2026
1875586
use ufl sobolev spaces
indiamai Mar 11, 2026
cb43446
lint
indiamai Jul 9, 2026
4a0191e
lint more
indiamai Jul 10, 2026
3df6d3f
Merge branch 'indiamai/uflsobolve' into indiamai/tensor_prd_refac
indiamai Jul 10, 2026
9eef0e4
Merge branch 'indiamai/tensor_prd_refac' of github.com:firedrakeproje…
indiamai Jul 10, 2026
b14a954
remove stale code
indiamai Jul 10, 2026
ad03d1a
fix numeric issue
indiamai Jul 10, 2026
3326d9e
lint
indiamai Jul 10, 2026
4aa8bec
fixing some tests
indiamai Jul 13, 2026
6791730
Add cross mesh test (#68)
indiamai Jul 13, 2026
bbc29a5
add more branches to matrix construction to eliminate vague warning
indiamai Jul 13, 2026
be67106
Merge branch 'main' into indiamai/tensor_prd_refac
indiamai Jul 13, 2026
a872169
change firedrake branch and update test
indiamai Jul 13, 2026
560a5bb
fix up hcurl and add test
indiamai Jul 13, 2026
f418482
hcurl converge test
indiamai Jul 13, 2026
71181d6
lint
indiamai Jul 14, 2026
1761197
tidying up for 3d tensor hdiv
indiamai Jul 15, 2026
76b18f0
comment out print
indiamai Jul 15, 2026
c9eee11
fix issue in enriched
indiamai Jul 15, 2026
98c5e71
add 3d hex hdiv
indiamai Jul 15, 2026
4fa92a8
hcurl 3d
indiamai Jul 15, 2026
b15860f
add convergence test for 3d
indiamai Jul 15, 2026
4a01b21
fix issue in ned edges
indiamai Jul 16, 2026
6e0a99d
remvoe stray breakpoint, tidy
indiamai Jul 16, 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
13 changes: 12 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,20 @@ jobs:
run: |
. venv/bin/activate
python3 -m pip install --break-system-packages -e './fiat-repo'

- uses: actions/checkout@v5
with:
repository: firedrakeproject/ufl
path: ufl-repo
ref: refs/heads/indiamai/hash_directional_sobolev

- name: Install checked out UFL
run: |
. venv/bin/activate
python3 -m pip install --break-system-packages -e './ufl-repo'

- name: Run tests
run: |
. venv/bin/activate
cd ./fuse-repo
firedrake-run-split-tests 1 1 -n 8 "$EXTRA_PYTEST_ARGS" ./test
firedrake-run-split-tests 1 1 -n 8 "$EXTRA_PYTEST_ARGS" ./test
321 changes: 213 additions & 108 deletions fuse/cells.py

Large diffs are not rendered by default.

22 changes: 9 additions & 13 deletions fuse/dof.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fuse.traces import TrH1
import numpy as np
import sympy as sp
import numbers


class Pairing():
Expand Down Expand Up @@ -35,7 +36,7 @@ def __call__(self, kernel, v, cell):
return v(*kernel.pt)

def tabulate(self):
return 1
return np.eye(self.entity.dim())

def add_entity(self, entity):
res = DeltaPairing()
Expand Down Expand Up @@ -80,8 +81,8 @@ def tabulate(self):
if self.orientation:
new_bvs = np.array(self.entity.orient(self.orientation).basis_vectors())
basis_change = np.matmul(np.linalg.inv(new_bvs), bvs)
return basis_change
return np.eye(bvs.shape[0])
return (1/self.entity.volume())*basis_change
return (1/self.entity.volume())*np.eye(bvs.shape[0])

def add_entity(self, entity):
res = L2Pairing()
Expand Down Expand Up @@ -194,7 +195,7 @@ def evaluate(self, Qpts, Qwts, basis_change, immersed, dim, value_shape):
comps = [[tuple()] for pt in Qpts]
else:
comps = [[(i,) for v in value_shape for i in range(v)] for pt in Qpts]
if isinstance(self.pt, tuple) or isinstance(self.pt, int):
if isinstance(self.pt, tuple) or isinstance(self.pt, numbers.Number):
return Qpts, np.array([wt*self.pt for wt in Qwts]).astype(np.float64), comps
if not immersed:
return Qpts, np.array([wt*np.matmul(self.pt, basis_change) for wt in Qwts]).astype(np.float64), comps
Expand Down Expand Up @@ -264,14 +265,14 @@ def evaluate(self, Qpts, bary_pts, Qwts, basis_change, immersed, dim, value_shap
return Qpts, np.array(wts).astype(np.float64), comps

def _to_dict(self):
o_dict = {"fn": self.fn}
o_dict = {"fn": self.fn, "syms": self.syms}
return o_dict

def dict_id(self):
return "BarycentricPolynomialKernel"

def _from_dict(obj_dict):
return BarycentricPolynomialKernel(obj_dict["fn"])
return BarycentricPolynomialKernel(obj_dict["fn"], symbols=obj_dict["syms"])


class PolynomialKernel(BaseKernel):
Expand Down Expand Up @@ -432,13 +433,7 @@ def convert_to_fiat(self, ref_el, interpolant_degree, value_shape=tuple()):
def to_quadrature(self, arg_degree, value_shape):
Qpts, Qwts = self.cell_defined_on.quadrature(self.kernel.degree(arg_degree))
Qwts = Qwts.reshape(Qwts.shape + (1,))
dim = self.cell_defined_on.get_spatial_dimension()
if dim > 0:
bvs = np.array(self.cell_defined_on.basis_vectors())
new_bvs = np.array(self.cell_defined_on.orient(self.pairing.orientation).basis_vectors())
basis_change = np.matmul(np.linalg.inv(new_bvs), bvs)
else:
basis_change = np.eye(dim)
basis_change = self.pairing.tabulate()

if self.immersed and (isinstance(self.kernel, VectorKernel) or isinstance(self.kernel, BarycentricPolynomialKernel) or isinstance(self.kernel, PolynomialKernel)):
def immersed(pt):
Expand Down Expand Up @@ -471,6 +466,7 @@ def immersed(pt):
J_det = self.cell.attachment_J_det(self.cell.id, self.cell_defined_on.id)
if not np.allclose(J_det, 1):
raise ValueError("Jacobian Determinant is not 1 did you do something wrong")
J_det = 1
# if self.pairing.orientation:
# immersion = self.target_space.tabulate(wts, self.pairing.entity.orient(self.pairing.orientation))[0]
# else:
Expand Down
9 changes: 4 additions & 5 deletions fuse/element_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ def vector_basis_fns(cell, deg, rot=False, interior_only=False):

All transformation groups are S1 as these are interior to the cell.
"""
print(cell, deg)
edge = cell.edges()[0]
face = cell.d_entities(2)[0]
facet_cell = edge
Expand All @@ -280,7 +279,7 @@ def vector_basis_fns(cell, deg, rot=False, interior_only=False):
xs1 = [DOF(L2Pairing(), BarycentricPolynomialKernel(pf_bf*new_bf(o), symbols=symbols))]
dofs += [DOFGenerator(xs1, pf_grp, S1)]
counter += (pf_grp).size()
print("facet dofs: ", counter)
# print("facet dofs: ", counter)
counter = 0
interior_deg = deg - 2

Expand All @@ -293,7 +292,7 @@ def vector_basis_fns(cell, deg, rot=False, interior_only=False):
counter += len(new_dofs)
dofs += [DOFGenerator(new_dofs, S1, S1)]
interior_deg = deg - 3
print("interior facet dofs:", counter)
# print("interior facet dofs:", counter)
counter = 0

basis_funcs, groups, symbols = lagrange_barycentric_basis(cell.dimension, cell.ordered_vertex_coords(), interior_deg)
Expand Down Expand Up @@ -328,8 +327,8 @@ def vector_basis_fns(cell, deg, rot=False, interior_only=False):
xs1 = [DOF(L2Pairing(), BarycentricPolynomialKernel(np.prod(symbols)*vec_bf*bf, symbols=symbols))]
dofs += [DOFGenerator(xs1, grp, g2)]
counter += (grp).size()
print("interior dofs:", counter)
print("end cell", cell)
# print("interior dofs:", counter)
# print("end cell", cell)
return dofs


Expand Down
29 changes: 27 additions & 2 deletions fuse/enriched.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ class EnrichedElement(ElementTriple):
In general, FUSE element triples should be represented nodally,
however this may not be possible for all constructions.

In particulr, we need to preserve tensor product structure.
In particular, we need to preserve tensor product structure.
"""

def __init__(self, A, B, flat=False, symmetric=True, matrices=True):
from fuse.tensor_products import TensorProductTriple
if not isinstance(A, TensorProductTriple) or not isinstance(B, TensorProductTriple):
valid_types = (TensorProductTriple, EnrichedElement)
if not isinstance(A, valid_types) or not isinstance(B, valid_types):
raise ValueError("EnrichedElement should only be used for Tensor product elements. Use + between triples for enrichment.")
self.A = A
self.B = B
Expand All @@ -25,6 +26,9 @@ def __init__(self, A, B, flat=False, symmetric=True, matrices=True):
if A.cell.flat != B.cell.flat:
raise ValueError("Tensor products must both be flat or both not flat for enrichment.")
self.cell = A.cell
self.flat = flat
if hasattr(A, "unflat_cell"):
self.unflat_cell = A.unflat_cell
self.symmetric = symmetric
self.apply_matrices = matrices
if self.apply_matrices:
Expand All @@ -36,9 +40,20 @@ def __init__(self, A, B, flat=False, symmetric=True, matrices=True):
def sub_elements(self):
return [self.A, self.B]

def get_value_shape(self):
if str(self.spaces[1]) in ("HDiv", "HCurl"):
return (self.cell.get_spatial_dimension(),)
return super().get_value_shape()

def __repr__(self):
return "Enriched(%s, %s)" % (repr(self.A), repr(self.B))

def __add__(self, other):
assert self.spaces[0].set_shape == other.spaces[0].set_shape
assert str(self.spaces[1]) == str(other.spaces[1])
return EnrichedElement(self, other, symmetric=self.symmetric and other.symmetric,
matrices=self.apply_matrices or other.apply_matrices)

def setup_matrices(self):
if self.cell.flat and not self.symmetric:
raise NotImplementedError("Matrices for flattened cells that are not symmetric not supported")
Expand All @@ -50,8 +65,12 @@ def setup_matrices(self):
else:
cell = self.cell
top = cell.to_fiat().get_topology()
seen_total_dims = set()
for dim in top.keys():
total_dim = sum(dim) if self.cell.flat else dim
if total_dim in seen_total_dims:
continue
seen_total_dims.add(total_dim)
ents = self.entity_dofs[total_dim].keys()
# comp_os = cell.component_orientations()
for e_idx, e in enumerate(ents):
Expand Down Expand Up @@ -87,3 +106,9 @@ def generate(self):
def to_ufl(self):
ufl_sub_elements = [e.to_ufl() for e in self.sub_elements]
return finat.ufl.EnrichedElement(*ufl_sub_elements, triple=self)

def flatten(self):
return EnrichedElement(self.A.flatten(), self.B.flatten(), flat=True, symmetric=self.symmetric, matrices=self.matrices)

def unflatten(self):
return EnrichedElement(self.A.unflatten(), self.B.unflatten(), flat=False, symmetric=self.symmetric, matrices=self.matrices)
10 changes: 10 additions & 0 deletions fuse/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ def __repr__(self):
return self.name + str(self.size())
return "GS" + str(self.size())

def _to_dict(self):
return {"members": [m.perm.array_form for m in self._members]}

def dict_id(self):
return "PermutationSet"

def _from_dict(o_dict):
perm_set = [Permutation(m) for m in o_dict["members"]]
return PermutationSetRepresentation(perm_set)


class GroupRepresentation(PermutationSetRepresentation):
"""
Expand Down
11 changes: 8 additions & 3 deletions fuse/serialisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(self):
"Edge": Edge,
"Triple": ElementTriple,
"Group": GroupRepresentation,
"PermutationSet": PermutationSetRepresentation,
"SobolevSpace": ElementSobolevSpace,
"InterpolationSpace": InterpolationSpace,
"PolynomialSpace": PolynomialSpace,
Expand All @@ -40,8 +41,11 @@ def __init__(self):
"DOFGen": DOFGenerator,
"Delta": DeltaPairing,
"L2Inner": L2Pairing,
"BarycentricPolynomialKernel": BarycentricPolynomialKernel,
"VectorKernel": VectorKernel,
"PolynomialKernel": PolynomialKernel,
"PointKernel": PointKernel,
"ComponentKernel": ComponentKernel,
"Trace": Trace
}

Expand All @@ -65,6 +69,10 @@ def encode_traverse(self, obj, path=[]):
res_array[i] = dfs_res
return res_array

# Some sympy objects are not hashable, so we must accept we may serialise them twice.
if isinstance(obj, sp.core.containers.Tuple) or isinstance(obj, sp.Expr) or isinstance(obj, sp.Matrix) or isinstance(obj, sp.Poly):
return "Sympy/" + sp.srepr(obj)

if obj in self.seen_objs.keys():
return self.seen_objs[obj]["id"]

Expand All @@ -75,9 +83,6 @@ def encode_traverse(self, obj, path=[]):
self.store_obj(obj, obj.dict_id(), obj_id, obj_dict, path)
return obj.dict_id() + "/" + str(obj_id)

if isinstance(obj, sp.core.containers.Tuple) or isinstance(obj, sp.Expr):
return "Sympy/" + sp.srepr(obj)

return obj

def get_id(self, obj):
Expand Down
6 changes: 6 additions & 0 deletions fuse/spaces/polynomial_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ def __hash__(self):
def restrict(self, mindegree, maxdegree):
return PolynomialSpace(maxdegree, contains=-1, mindegree=mindegree, set_shape=self.set_shape)

def to_vector(self):
return PolynomialSpace(self.maxdegree, self.contains, self.mindegree, set_shape=True)

def _to_dict(self):
return {"set_shape": self.set_shape, "min": self.mindegree, "contains": self.contains, "max": self.maxdegree}

Expand Down Expand Up @@ -231,6 +234,9 @@ def __add__(self, x):
s.extend([x])
return ConstructedPolynomialSpace(w, s)

def to_vector(self):
return ConstructedPolynomialSpace(self.weights, [space.to_vector() for space in self.spaces])

def _to_dict(self):
super_dict = super(ConstructedPolynomialSpace, self)._to_dict()
super_dict["spaces"] = self.spaces
Expand Down
Loading
Loading