forked from FEniCS/fiat
-
Notifications
You must be signed in to change notification settings - Fork 7
Expressing barycentric coordinates symbolically in GEM #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
achanbour
wants to merge
22
commits into
main
Choose a base branch
from
achanbour/bary-coords
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
828691b
added new methods for computing barycentric coords on tensor-product …
achanbour 85e17ef
minor fixes
achanbour 0b38a37
modified compute_barycentric_coords to consistently handle input poin…
achanbour 78db95e
extended the tensor product barycentric coordinates computation funct…
achanbour 4f3d9cd
removed line enforcing points to be numpy arrays to ensure code gener…
achanbour 4677313
added conversion to numpy array only in axis_barycentric_coords
achanbour c19b8bd
generalised numpy operations in compute_barycentric_coordinates metho…
achanbour ac0d93e
changed compute_barycentric_coordinates method to work on input GEM e…
achanbour cf47fe9
extended gem matmul to convert numpy arrays to Literals and deal sepa…
achanbour c04b6fd
changes made to gem and FIAT to make barycentric coordinate computati…
achanbour f339b01
extended gem's slicing syntax and simplified the code in compute_axis…
achanbour ddf6eb4
tidied up and added comments
achanbour 65408bd
Merge remote-tracking branch 'origin/main' into achanbour/bary-coords
achanbour 7b35ca3
recent changes post merging
achanbour 5919d61
compute barycentric coords symbolically in simplicies and hypercubes …
achanbour 3d87fc1
modified unit tests for computing bary coords on points passed as num…
achanbour 76e63f7
latest changes + gem tests
achanbour e2403c4
removed egg-info from tracking
achanbour 26e9f39
implemented handler in gem.evaluate for FlexiblyIndexed nodes
achanbour d9a5e4b
added a new ListIndex type to GEM for indexing GEM tensors using an i…
achanbour 05ea454
final changes
achanbour 0cb0398
fixed formatting
achanbour File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,3 +13,5 @@ release/ | |
| /docs/source/finat.rst | ||
| /docs/source/finat.ufl.rst | ||
| /docs/source/gem.rst | ||
|
|
||
| firedrake_fiat.egg-info/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -615,8 +615,10 @@ def get_dimension(self): | |||||
|
|
||||||
| def compute_barycentric_coordinates(self, points, entity=None, rescale=False): | ||||||
| """Returns the barycentric coordinates of a list of points on the complex.""" | ||||||
| if len(points) == 0: | ||||||
|
|
||||||
| if isinstance(points, numpy.ndarray) and len(points) == 0: | ||||||
| return points | ||||||
|
|
||||||
| if entity is None: | ||||||
| entity = (self.get_spatial_dimension(), 0) | ||||||
| entity_dim, entity_id = entity | ||||||
|
|
@@ -640,8 +642,11 @@ def compute_barycentric_coordinates(self, points, entity=None, rescale=False): | |||||
| h = 1 / numpy.linalg.norm(A, axis=1) | ||||||
| b *= h | ||||||
| A *= h[:, None] | ||||||
| out = numpy.dot(points, A.T) | ||||||
| return numpy.add(out, b, out=out) | ||||||
| # out = numpy.dot(points, A.T) | ||||||
| out = points @ A.T | ||||||
|
|
||||||
| # return numpy.add(out, b) | ||||||
| return out + b | ||||||
|
|
||||||
| def compute_bubble(self, points, entity=None): | ||||||
| """Returns the lowest-order bubble on an entity evaluated at the given | ||||||
|
|
@@ -1406,6 +1411,41 @@ def extrinsic_orientation_permutation_map(self): | |||||
| def is_macrocell(self): | ||||||
| return any(c.is_macrocell() for c in self.cells) | ||||||
|
|
||||||
| def compute_axis_barycentric_coordinates(self, points, entity=None, rescale=False): | ||||||
| """Compute barycentric coordinates on each axis (factor) of a tensor-product cell. | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| points: numpy.ndarray or GEM.Node | ||||||
| The reference coordinates of the points. | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| numpy.ndarray | ||||||
| A flattened array of shape ``(total_bary_coords, )`` and dtype object if points are GEM nodes, | ||||||
| otherwise dtype numeric. The i-th entry contains the barycentric coordinates | ||||||
| on the i-th factor cell. If factor i is a simplex of dimension d, this will | ||||||
| have shape ``(npoints, d+1)``. If factor i is a hypercube of dimension d, | ||||||
| this will have shape ``(npoints, 2*d)``. | ||||||
| """ | ||||||
|
|
||||||
| if isinstance(points, numpy.ndarray) and len(points) == 0: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| return points | ||||||
|
|
||||||
| axis_dims = [c.get_spatial_dimension() for c in self.cells] | ||||||
| point_slices = TensorProductCell._split_slices(axis_dims) | ||||||
|
|
||||||
| result = numpy.empty(len(self.cells), dtype=object) | ||||||
| for k, (factor, s) in enumerate(zip(self.cells, point_slices)): | ||||||
| result[k] = factor.compute_barycentric_coordinates(points[..., s], entity, rescale) | ||||||
|
|
||||||
| # Flatten the array | ||||||
| # We cannot construct the flat array directly since we may not know upfront the total number | ||||||
| # of barycentric coordinates (e.g., in a simplex it is d+1, in a hypercube it is 2*d) | ||||||
| flat_result = numpy.array([bary[j] for bary in result for j in range(bary.shape[0])]) | ||||||
|
|
||||||
| return flat_result | ||||||
|
|
||||||
|
|
||||||
| class Hypercube(Cell): | ||||||
| """Abstract class for a reference hypercube""" | ||||||
|
|
@@ -1423,6 +1463,8 @@ def __init__(self, dimension, product): | |||||
| self.product = product | ||||||
| self.unflattening_map = compute_unflattening_map(pt) | ||||||
|
|
||||||
| self.facet_perm = compute_facet_permutation(self.unflattening_map, self.product) | ||||||
|
|
||||||
| def get_dimension(self): | ||||||
| """Returns the subelement dimension of the cell. Same as the | ||||||
| spatial dimension.""" | ||||||
|
|
@@ -1521,6 +1563,27 @@ def __ge__(self, other): | |||||
| def __le__(self, other): | ||||||
| return self.product <= other | ||||||
|
|
||||||
| def compute_barycentric_coordinates(self, points, entity=None, rescale=False): | ||||||
| """Returns the barycentric coordinates of a list of points on the hypercube. | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| points: numpy.ndarray or GEM.Node | ||||||
| The reference coordinates of the points. | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| List of numpy.ndarray or GEM.ComponentTensor | ||||||
| Returns a list of barycentric coordinates in local facet order such that for any point | ||||||
| lying on local facet `lf` of the cell, the barycentric coordinate at index `lf` vanishes. | ||||||
| """ | ||||||
| if isinstance(points, numpy.ndarray) and len(points) == 0: | ||||||
| return points | ||||||
|
|
||||||
| tp_bary_coords = self.product.compute_axis_barycentric_coordinates(points, entity, rescale) | ||||||
|
|
||||||
| return tp_bary_coords[self.facet_perm] | ||||||
|
|
||||||
|
|
||||||
| class UFCHypercube(Hypercube): | ||||||
| """Reference UFC Hypercube | ||||||
|
|
@@ -1839,6 +1902,60 @@ def compute_unflattening_map(topology_dict): | |||||
| return unflattening_map | ||||||
|
|
||||||
|
|
||||||
| def compute_facet_permutation(unflattening_map, product): | ||||||
| """ | ||||||
| Returns a permutation mapping each facet of a `~.Hypercube` to the index of the | ||||||
| barycentric coordinate that vanishes on it. | ||||||
|
|
||||||
| The order of barycentric coordinates returned by `compute_axis_barycentric_coordinates` | ||||||
| is determined by axis structure, not by facet numbering. Reordering them by this permutation | ||||||
| yields the invariant: the i-th barycentric coordinate vanishes on the i-th facet. | ||||||
| """ | ||||||
| # First compute axis offsets into the flattened barycentric coordinate array. | ||||||
| axis_offsets = [] | ||||||
| offset = 0 | ||||||
| for axis_cell in product.cells: | ||||||
| axis_offsets.append(offset) | ||||||
| offset += axis_cell.get_dimension() + 1 | ||||||
|
|
||||||
| # Initialise the integer permutation array | ||||||
| sd = len(product.cells) | ||||||
| num_facets = 2 * sd | ||||||
| perm = numpy.zeros(num_facets, dtype=int) | ||||||
|
|
||||||
| for f in range(num_facets): | ||||||
| # Recover the tensor-product representation of the facet as given by the unflattening map | ||||||
| dim_tuple, tp_entity = unflattening_map[(sd - 1, f)] | ||||||
|
|
||||||
| # Determine the axis that's orthogonal to the facet | ||||||
| # E.g., in a quad: | ||||||
| # if dim_tuple = (0,1) -> facet has dimension 0 on the first component -> fixed at x = 0 or x = 1 | ||||||
| # if dim_tuple = (1,0) -> facet has dimension 0 on the second component -> fixed at y = 0 or y = 1 | ||||||
| axis = next( | ||||||
| i for i, d in enumerate(dim_tuple) | ||||||
| if d == product.cells[i].get_dimension() - 1 | ||||||
| ) | ||||||
|
|
||||||
| # Determine the index of the endpoint that produces the facet | ||||||
| # which gives the local facet number in the axis space | ||||||
| entity_shape = tuple( | ||||||
| len(c.get_topology()[d]) | ||||||
| for c, d in zip(product.cells, dim_tuple) | ||||||
| ) | ||||||
| tuple_ei = numpy.unravel_index(tp_entity, entity_shape) | ||||||
| local_facet = tuple_ei[axis] | ||||||
|
|
||||||
| # For a simplex (UFCInterval, UFCTriangle), the barycentric coordinate that vanishes on local facet i | ||||||
| # corresponds to the ID of the vertex that doesn't belong to that facet | ||||||
| all_vertices = set(product.cells[axis].get_topology()[0].keys()) | ||||||
| facet_vertices = set(product.cells[axis].get_topology()[0][local_facet]) | ||||||
| bary_index = next(iter(all_vertices - facet_vertices)) | ||||||
|
|
||||||
| perm[f] = axis_offsets[axis] + bary_index | ||||||
|
|
||||||
| return perm | ||||||
|
|
||||||
|
|
||||||
| def max_complex(complexes): | ||||||
| max_cell = max(complexes) | ||||||
| if all(max_cell >= b for b in complexes): | ||||||
|
|
||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.