Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6573217
First draft of expanding set size to match when unioning
indiamai Aug 19, 2024
dc5188a
Change condition of extension
indiamai Aug 19, 2024
c75efef
Adapt new coeff construction for vec sets
indiamai Aug 20, 2024
dfd1347
select larger of degrees
indiamai Aug 21, 2024
21f1bf1
Add orthonormal requirement
indiamai Aug 28, 2024
3eb0d1a
merge master
indiamai Nov 18, 2024
2c21e27
Merge master
indiamai Dec 9, 2024
2b45bd5
Move finat commits from old branch
indiamai Dec 9, 2024
9b05688
Lint
indiamai Dec 9, 2024
3a7bb5b
Merge master
indiamai Jan 6, 2025
8d1a074
Refactor to deal with single source of truth
indiamai Jan 6, 2025
b72cd61
Refactoring
indiamai Jan 6, 2025
a89d057
Renaming fuse project (#125)
indiamai Jan 10, 2025
ac81474
Address Pablo's comments
indiamai Jan 11, 2025
9420a6d
Lint
indiamai Jan 11, 2025
be81de3
Remove final references to India Def
indiamai Jan 11, 2025
2e3ed6c
Add test, small change to spatial dimension management
indiamai Jan 13, 2025
0b593e5
remove fuse dependency
indiamai Jan 15, 2025
263dad1
Modify code to use np.pad
indiamai Jan 16, 2025
ad63ee0
Add extension of coefficients to allow union of polysets with differe…
indiamai Jan 16, 2025
3a5b073
Small changes to allow different topologies
indiamai Jan 17, 2025
8d52e22
remove orthonormal as seems to be no longer necessary
indiamai Jan 22, 2025
8eaa60c
Remove further orthonormals
indiamai Jan 23, 2025
d3b31cb
Merge branch 'indiamai/extend_coeffs' into indiamai/integrate_fuse
indiamai Jan 23, 2025
b5e41fe
Merge branch 'master' into indiamai/integrate_fuse
indiamai Jan 26, 2025
f31fd72
Update UFL rep of fuse element to cover both regular fuse and tensor …
indiamai Feb 10, 2025
a91c7a2
tensor prod infrastructure
indiamai Feb 17, 2025
2bc3161
convert things to generic as_Cell and add flattening infra for fuse
indiamai Feb 20, 2025
4953241
Add generic hypercube class
indiamai Feb 21, 2025
e8aaa56
Change type to allow comparision of subclasses of tensor product cell…
indiamai Feb 26, 2025
a726c98
Add function that moves any cell to a simplex. Modify DPC to use
indiamai Mar 13, 2025
7d5ef1c
Integrate hypercube changes to fuse (#137)
indiamai Mar 21, 2025
9e2f7c0
Merge branch 'master' into indiamai/integrate_fuse
indiamai Apr 30, 2025
2f8a74d
Merge branch 'master' into indiamai/integrate_fuse
indiamai Apr 30, 2025
8771342
First stab at a rewrite of connectivity to respect non UFC ordering
indiamai May 5, 2025
2373db4
refactor, simplify
indiamai May 6, 2025
3482800
remove print
indiamai May 7, 2025
ef6c59c
modify to use hasse diagram for fuse sub entities
indiamai May 16, 2025
df581a7
remove print
indiamai May 16, 2025
dde1c05
add the ablity to pass facet orientation to facet quadrature rule
indiamai May 23, 2025
f04175e
Merge branch 'master' into indiamai/integrate_fuse
indiamai Jun 12, 2025
ab73328
add clearer error
indiamai Jun 12, 2025
85cef62
Merge branch 'main' into indiamai/integrate_fuse
indiamai Sep 25, 2025
e665761
Move fuse as_cell dependence out of UFL and into final.ufl (#184)
indiamai Oct 1, 2025
53bc284
Merge branch 'main' into indiamai/integrate_fuse
indiamai Oct 30, 2025
a4a887c
Merge branch 'main' into indiamai/integrate_fuse
indiamai Oct 30, 2025
4633e7f
Merge branch 'main' into indiamai/integrate_fuse
indiamai Dec 3, 2025
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
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ jobs:
run: DATA_REPO_GIT="" python -m pytest --cov=FIAT/ test/FIAT
- name: Test FInAT
run: DATA_REPO_GIT="" python -m pytest --cov=finat/ --cov=gem/ test/finat
- name: Test FInAT with FUSE
run: |
FIREDRAKE_USE_FUSE=1 DATA_REPO_GIT="" python -m pytest test/finat
28 changes: 8 additions & 20 deletions FIAT/discontinuous_pc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,17 @@
# Modified by David A. Ham (david.ham@imperial.ac.uk), 2018

from FIAT import finite_element, polynomial_set, dual_set, functional
from FIAT.reference_element import (Point,
DefaultLine,
UFCInterval,
UFCQuadrilateral,
UFCHexahedron,
UFCTriangle,
UFCTetrahedron,
make_affine_mapping,
flatten_reference_cube)
from FIAT.reference_element import (make_affine_mapping,
flatten_reference_cube,
cell_to_simplex)
from FIAT.P0 import P0Dual
import numpy as np

hypercube_simplex_map = {Point(): Point(),
DefaultLine(): DefaultLine(),
UFCInterval(): UFCInterval(),
UFCQuadrilateral(): UFCTriangle(),
UFCHexahedron(): UFCTetrahedron()}


class DPC0(finite_element.CiarletElement):
def __init__(self, ref_el):
flat_el = flatten_reference_cube(ref_el)
poly_set = polynomial_set.ONPolynomialSet(hypercube_simplex_map[flat_el], 0)
poly_set = polynomial_set.ONPolynomialSet(cell_to_simplex(flat_el), 0)
dual = P0Dual(ref_el)
# Implement entity_permutations when we handle that for HigherOrderDPC.
# Currently, orientation_tuples in P0Dual(ref_el).entity_permutations
Expand Down Expand Up @@ -58,7 +46,7 @@ def __init__(self, ref_el, flat_el, degree):

# Change coordinates here.
# Vertices of the simplex corresponding to the reference element.
v_simplex = hypercube_simplex_map[flat_el].get_vertices()
v_simplex = cell_to_simplex(flat_el).get_vertices()
# Vertices of the reference element.
v_hypercube = flat_el.get_vertices()
# For the mapping, first two vertices are unchanged in all dimensions.
Expand All @@ -74,12 +62,12 @@ def __init__(self, ref_el, flat_el, degree):

# make nodes by getting points
# need to do this dimension-by-dimension, facet-by-facet
top = hypercube_simplex_map[flat_el].get_topology()
top = cell_to_simplex(flat_el).get_topology()

cur = 0
for dim in sorted(top):
for entity in sorted(top[dim]):
pts_cur = hypercube_simplex_map[flat_el].make_points(dim, entity, degree)
pts_cur = cell_to_simplex(flat_el).make_points(dim, entity, degree)
pts_cur = [tuple(np.matmul(A, np.array(x)) + b) for x in pts_cur]
nodes_cur = [functional.PointEvaluation(flat_el, x)
for x in pts_cur]
Expand All @@ -102,7 +90,7 @@ class HigherOrderDPC(finite_element.CiarletElement):

def __init__(self, ref_el, degree):
flat_el = flatten_reference_cube(ref_el)
poly_set = polynomial_set.ONPolynomialSet(hypercube_simplex_map[flat_el], degree)
poly_set = polynomial_set.ONPolynomialSet(cell_to_simplex(flat_el), degree)
dual = DPCDualSet(ref_el, flat_el, degree)
formdegree = flat_el.get_spatial_dimension() # n-form
super().__init__(poly_set=poly_set,
Expand Down
2 changes: 1 addition & 1 deletion FIAT/lagrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, ref_el, degree, point_variant="equispaced", sort_entities=Fal
entities = [(dim, entity) for dim in sorted(top) for entity in sorted(top[dim])]
if sort_entities:
# sort the entities by support vertex ids
support = [top[dim][entity] for dim, entity in entities]
support = [sorted(top[dim][entity]) for dim, entity in entities]
entities = [entity for verts, entity in sorted(zip(support, entities))]

# make nodes by getting points
Expand Down
4 changes: 2 additions & 2 deletions FIAT/nedelec.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def NedelecSpace2D(ref_el, degree):
raise Exception("NedelecSpace2D requires 2d reference element")

k = degree - 1
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,), scale="orthonormal")

dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
dimPk = expansions.polynomial_dimension(ref_el, k)
Expand All @@ -32,7 +32,7 @@ def NedelecSpace2D(ref_el, degree):
for i in range(sd))))
vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)

Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, scale="orthonormal")
PkH = Pkp1.take(list(range(dimPkm1, dimPk)))

Q = create_quadrature(ref_el, 2 * (k + 1))
Expand Down
4 changes: 3 additions & 1 deletion FIAT/quadrature.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,12 @@ class CollapsedQuadratureTetrahedronRule(CollapsedQuadratureSimplexRule):
class FacetQuadratureRule(QuadratureRule):
"""A quadrature rule on a facet mapped from a reference quadrature rule.
"""
def __init__(self, ref_el, entity_dim, entity_id, Q_ref):
def __init__(self, ref_el, entity_dim, entity_id, Q_ref, facet_orientation=None):
# Construct the facet of interest
facet = ref_el.construct_subelement(entity_dim)
facet_topology = ref_el.get_topology()[entity_dim][entity_id]
if facet_orientation:
facet_topology = tuple(facet_orientation.permute(list(facet_topology)))
facet.vertices = ref_el.get_vertices_of_subcomplex(facet_topology)

# Map reference points and weights on the appropriate facet
Expand Down
4 changes: 2 additions & 2 deletions FIAT/raviart_thomas.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def RTSpace(ref_el, degree):
sd = ref_el.get_spatial_dimension()

k = degree - 1
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,), scale="orthonormal")

dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
dimPk = expansions.polynomial_dimension(ref_el, k)
Expand All @@ -30,7 +30,7 @@ def RTSpace(ref_el, degree):
for i in range(sd))))
vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)

Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, scale="orthonormal")
PkH = Pkp1.take(list(range(dimPkm1, dimPk)))

Q = create_quadrature(ref_el, 2 * (k + 1))
Expand Down
87 changes: 58 additions & 29 deletions FIAT/reference_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class Cell:
"""Abstract class for a reference cell. Provides accessors for
geometry (vertex coordinates) as well as topology (orderings of
vertices that make up edges, faces, etc."""
def __init__(self, shape, vertices, topology):
def __init__(self, shape, vertices, topology, sub_entities=None):
"""The constructor takes a shape code, the physical vertices expressed
as a list of tuples of numbers, and the topology of a cell.

Expand All @@ -145,24 +145,42 @@ def __init__(self, shape, vertices, topology):
self.vertices = vertices
self.topology = topology

# Given the topology, work out for each entity in the cell,
# which other entities it contains.
self.sub_entities = {}
for dim, entities in topology.items():
self.sub_entities[dim] = {}

for e, v in entities.items():
vertices = frozenset(v)
sub_entities = []

for dim_, entities_ in topology.items():
for e_, vertices_ in entities_.items():
if vertices.issuperset(vertices_):
sub_entities.append((dim_, e_))

# Sort for the sake of determinism and by UFC conventions
self.sub_entities[dim][e] = sorted(sub_entities)

if sub_entities:
self.sub_entities = sub_entities
else:
# If sub entity list not provided
# Given the topology, work out for each entity in the cell,
# which other entities it contains.
self.sub_entities = {}
self.sub_entities_old = {}
for dim, entities in topology.items():
self.sub_entities[dim] = {}
self.sub_entities_old[dim] = {}

for e, v in entities.items():
vertices = frozenset(v)
sub_entities = []
sub_entities_old = []
for dim_, entities_ in topology.items():
for e_, vertices_ in entities_.items():
if vertices.issuperset(vertices_):
sub_entities_old.append((dim_, e_))

# in order to maintain ordering, extract subentities from vertex numbering
entities_of_dim_ = list(entities_.values())

from itertools import permutations
# generate all possible sub entities
sub_list = permutations(v, len(entities_of_dim_[0]))
for s in sub_list:
# add the sub entities in the same order as in topology
for i, val in entities_.items():
if set(s) == set(val) and (dim_, i) not in sub_entities:
sub_entities.append((dim_, i))

self.sub_entities[dim][e] = list(sub_entities)
self.sub_entities_old[dim][e] = list(sub_entities_old)
self.sub_entities = self.sub_entities_old
# Build super-entity dictionary by inverting the sub-entity dictionary
self.super_entities = {dim: {entity: [] for entity in topology[dim]} for dim in topology}
for dim0 in topology:
Expand All @@ -183,7 +201,6 @@ def __init__(self, shape, vertices, topology):
neighbors = children if dim1 < dim0 else parents
d01_entities = tuple(e for d, e in neighbors if d == dim1)
self.connectivity[(dim0, dim1)].append(d01_entities)

# Dictionary with derived cells
self._split_cache = {}

Expand Down Expand Up @@ -387,14 +404,14 @@ class SimplicialComplex(Cell):

This consists of list of vertex locations and a topology map defining facets.
"""
def __init__(self, shape, vertices, topology):
def __init__(self, shape, vertices, topology, sub_ents=None):
# Make sure that every facet has the right number of vertices to be
# a simplex.
for dim in topology:
for entity in topology[dim]:
assert len(topology[dim][entity]) == dim + 1

super().__init__(shape, vertices, topology)
super().__init__(shape, vertices, topology, sub_ents)

def compute_normal(self, facet_i, cell=None):
"""Returns the unit normal vector to facet i of codimension 1."""
Expand Down Expand Up @@ -528,7 +545,8 @@ def make_points(self, dim, entity_id, order, variant=None, interior=1):
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()[self.get_topology()[dim][entity_id][0]],)
# return (self.get_vertices()[entity_id], )
Comment on lines +548 to +549

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (self.get_vertices()[self.get_topology()[dim][entity_id][0]],)
# return (self.get_vertices()[entity_id], )
vid, = self.topology[dim][entity_id]
return (self.get_vertices()[vid], )

elif 0 < dim <= self.get_spatial_dimension():
entity_verts = \
self.get_vertices_of_subcomplex(
Expand Down Expand Up @@ -1144,7 +1162,7 @@ def compute_normal(self, i):
class TensorProductCell(Cell):
"""A cell that is the product of FIAT cells."""

def __init__(self, *cells):
def __init__(self, *cells, sub_entities=None):
# Vertices
vertices = tuple(tuple(chain(*coords))
for coords in product(*[cell.get_vertices()
Expand All @@ -1167,7 +1185,7 @@ def __init__(self, *cells):
topology[dim] = dict(enumerate(topology[dim][key]
for key in sorted(topology[dim])))

super().__init__(TENSORPRODUCT, vertices, topology)
super().__init__(TENSORPRODUCT, vertices, topology, sub_entities)
self.cells = tuple(cells)

def __repr__(self):
Expand Down Expand Up @@ -1317,10 +1335,14 @@ def compare(self, op, other):
This is done dimension by dimension."""
if hasattr(other, "product"):
other = other.product
if isinstance(other, type(self)):
if isinstance(other, TensorProductCell):
return all(op(a, b) for a, b in zip(self.cells, other.cells))
else:
return op(self, other)
if op == operator.gt or op == operator.lt or operator.ne:
return not op(other, self)
if op == operator.ge or op == operator.le:
return not op(other, self) or operator.eq(self, other)
raise ValueError("Unknown operator in cell comparison")

def __gt__(self, other):
return self.compare(operator.gt, other)
Expand Down Expand Up @@ -1410,15 +1432,15 @@ def is_macrocell(self):
class Hypercube(Cell):
"""Abstract class for a reference hypercube"""

def __init__(self, dimension, product):
def __init__(self, dimension, product, sub_entities=None):
self.dimension = dimension
self.shape = hypercube_shapes[dimension]

pt = product.get_topology()
verts = product.get_vertices()
topology = flatten_entities(pt)

super().__init__(self.shape, verts, topology)
super().__init__(self.shape, verts, topology, sub_entities)

self.product = product
self.unflattening_map = compute_unflattening_map(pt)
Expand Down Expand Up @@ -1845,3 +1867,10 @@ def max_complex(complexes):
return max_cell
else:
raise ValueError("Cannot find the maximal complex")


def cell_to_simplex(cell):
if cell.is_simplex():
return cell
else:
return ufc_simplex(cell.get_dimension())
3 changes: 2 additions & 1 deletion finat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
Lagrange, Real, Serendipity, # noqa: F401
TrimmedSerendipityCurl, TrimmedSerendipityDiv, # noqa: F401
TrimmedSerendipityEdge, TrimmedSerendipityFace, # noqa: F401
Nedelec, NedelecSecondKind, RaviartThomas, Regge) # noqa: F401
Nedelec, NedelecSecondKind, RaviartThomas, Regge, # noqa: F401
FuseElement) # noqa: F401
from .spectral import (GaussLobattoLegendre, GaussLegendre, KongMulderVeldhuizen, # noqa: F401
Legendre, IntegratedLegendre, # noqa: F401
FDMLagrange, FDMQuadrature, FDMDiscontinuousLagrange, # noqa: F401
Expand Down
9 changes: 4 additions & 5 deletions finat/cube.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from __future__ import absolute_import, division, print_function

from FIAT.reference_element import (UFCHexahedron, UFCQuadrilateral,
compute_unflattening_map, flatten_entities,
from FIAT.reference_element import (compute_unflattening_map, flatten_entities,
flatten_permutations)
from FIAT.tensor_product import FlattenedDimensions as FIAT_FlattenedDimensions
from gem.utils import cached_property

from finat.finiteelementbase import FiniteElementBase
from finat.element_factory import as_fiat_cell


class FlattenedDimensions(FiniteElementBase):
Expand All @@ -23,9 +22,9 @@ def __init__(self, element):
def cell(self):
dim = self.product.cell.get_spatial_dimension()
if dim == 2:
return UFCQuadrilateral()
return as_fiat_cell("quadrilateral")
elif dim == 3:
return UFCHexahedron()
return as_fiat_cell("hexahedron")
else:
raise NotImplementedError("Cannot guess cell for spatial dimension %s" % dim)

Expand Down
25 changes: 22 additions & 3 deletions finat/element_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import ufl

from FIAT import ufc_cell
from FIAT.reference_element import TensorProductCell

__all__ = ("as_fiat_cell", "create_base_element",
"create_element", "supported_elements")
Expand Down Expand Up @@ -110,9 +111,18 @@ def as_fiat_cell(cell):
"""Convert a ufl cell to a FIAT cell.

:arg cell: the :class:`ufl.Cell` to convert."""
if isinstance(cell, str):
cell = finat.ufl.as_cell(cell)
if not isinstance(cell, ufl.AbstractCell):
raise ValueError("Expecting a UFL Cell")
return ufc_cell(cell)
if isinstance(cell, ufl.TensorProductCell) and any([hasattr(c, "to_fiat") for c in cell._cells]):
if not all([hasattr(c, "to_fiat") for c in cell._cells]):
raise NotImplementedError("FUSE defined cells cannot be tensor producted with FIAT defined cells")
return TensorProductCell(*[c.to_fiat() for c in cell._cells])
try:
return cell.to_fiat()
except AttributeError:
return ufc_cell(cell)


@singledispatch
Expand Down Expand Up @@ -320,8 +330,17 @@ def convert_restrictedelement(element, **kwargs):
return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps


hexahedron_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval, ufl.interval)
quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval)
@convert.register(finat.ufl.FuseElement)
def convert_fuse_element(element, **kwargs):
if element.triple.flat:
new_elem = element.triple.unflatten()
finat_elem, deps = _create_element(new_elem.to_ufl(), **kwargs)
return finat.FlattenedDimensions(finat_elem), deps
return finat.fiat_elements.FuseElement(element.triple), set()


hexahedron_tpc = ufl.TensorProductCell(finat.ufl.as_cell("interval"), finat.ufl.as_cell("interval"), finat.ufl.as_cell("interval"))
quadrilateral_tpc = ufl.TensorProductCell(finat.ufl.as_cell("interval"), finat.ufl.as_cell("interval"))
_cache = weakref.WeakKeyDictionary()


Expand Down
5 changes: 5 additions & 0 deletions finat/fiat_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,8 @@ def __init__(self, cell, degree, **kwargs):
class NedelecSecondKind(VectorFiatElement):
def __init__(self, cell, degree, **kwargs):
super().__init__(FIAT.NedelecSecondKind(cell, degree, **kwargs))


class FuseElement(FiatElement):
def __init__(self, triple):
super(FuseElement, self).__init__(triple.to_fiat())
Loading
Loading