Computing barycentric coordinates in Tensor Product Cells#246
Computing barycentric coordinates in Tensor Product Cells#246achanbour wants to merge 9 commits into
Conversation
|
Looks like you are including commits form #240. Are they needed? |
448c540 to
d71fa37
Compare
…cells minor fixes modified compute_barycentric_coords to consistently handle input points of different sizes extended the tensor product barycentric coordinates computation function to handle nested tensor-product cells + added tests removed line enforcing points to be numpy arrays to ensure code generation works properly for symbolic points added conversion to numpy array only in axis_barycentric_coords generalised numpy operations in compute_barycentric_coordinates methods to work on different array shapes changed compute_barycentric_coordinates method to work on input GEM expressions representing point sets extended gem matmul to convert numpy arrays to Literals and deal separately with singleton contractions changes made to gem and FIAT to make barycentric coordinate computations work on GEM tensor expressions extended gem's slicing syntax and simplified the code in compute_axis_barycentric_coordinates in tp cells tidied up and added comments recent changes post merging compute barycentric coords symbolically in simplicies and hypercubes (to be tested) modified unit tests for computing bary coords on points passed as numpy arrays latest changes + gem tests implemented handler in gem.evaluate for FlexiblyIndexed nodes added a new ListIndex type to GEM for indexing GEM tensors using an index array final changes fixed formatting
changed ListIndex to be a subclass of Index
separated ListIndex from Index
…plicies and tensor product cells
… sub-entities of simplicies + added notes + modified test checking facet ordering of bary coords on tp cells modified the index substitution logic to correctly handle ListIndex changed index substitution optim isation
d71fa37 to
a3d51a1
Compare
No. I had accidentally merged some irrelevant changes into the old branch from which this PR was branched off. I've now reviewed the changes and removed unnecessary commits. |
| subcomplex = global_indices | ||
|
|
||
| if entity_dim != sd: |
There was a problem hiding this comment.
| subcomplex = global_indices | |
| if entity_dim != sd: | |
| if entity_dim == sd: | |
| subcomplex = global_indices | |
| else: |
| cell_id = self.connectivity[(entity_dim, sd)][entity_id][0] | ||
|
|
||
| # restrict indices to the chosen sub-cell | ||
| indices = [i for i, v in enumerate(top[sd][cell_id]) if v in subcomplex] |
There was a problem hiding this comment.
| indices = [i for i, v in enumerate(top[sd][cell_id]) if v in subcomplex] | |
| indices = [i for i, v in enumerate(top[sd][cell_id]) if v in global_indices] |
| facet_ids = self.connectivity[(entity_dim, entity_dim-1)][entity_id] | ||
| facets = [top[entity_dim-1][f] for f in facet_ids] # facet as a tuple of vertex indices that form it | ||
|
|
||
| perm = [facets.index(tuple(sorted(set(global_indices) - {v}))) for v in global_indices] |
There was a problem hiding this comment.
I think we need to permute indices with the inverse of perm. facets.index returns a facet index, but indices refers to vertices.
| cell_verts = self.get_vertices_of_subcomplex(subcomplex) # get vertex coordinates of the sub-cell | ||
| ref_verts = numpy.eye(sd + 1) # get vertex coordinates of the standard reference cell |
There was a problem hiding this comment.
To keep a cleaner diff, it is better to not add extra comments on unmodified lines
| cell_verts = self.get_vertices_of_subcomplex(subcomplex) # get vertex coordinates of the sub-cell | |
| ref_verts = numpy.eye(sd + 1) # get vertex coordinates of the standard reference cell | |
| # Get vertex coordinates of the physical and reference sub-cells | |
| cell_verts = self.get_vertices_of_subcomplex(subcomplex) | |
| ref_verts = numpy.eye(sd + 1) |
| result = [] | ||
| for factor, s in zip(self.cells, point_slices): | ||
| factor_sd = factor.get_spatial_dimension() | ||
| if factor_sd == 1: | ||
| factor_bary_coords = factor.compute_barycentric_coordinates(points[..., s], entity, rescale, facet_ordering=True) | ||
| else: | ||
| # For higher dimensional simplicies: vertex ordering is already guaranteed. | ||
| # NOTE: But what if we want to compute barycentric coordinates on a sub-entity and still want facet ordering? | ||
| # Then we need to pass facet_ordering=True even when factor_sd > 1 | ||
| factor_bary_coords = factor.compute_barycentric_coordinates(points[..., s], entity, rescale) | ||
| result.append(factor_bary_coords) |
There was a problem hiding this comment.
| result = [] | |
| for factor, s in zip(self.cells, point_slices): | |
| factor_sd = factor.get_spatial_dimension() | |
| if factor_sd == 1: | |
| factor_bary_coords = factor.compute_barycentric_coordinates(points[..., s], entity, rescale, facet_ordering=True) | |
| else: | |
| # For higher dimensional simplicies: vertex ordering is already guaranteed. | |
| # NOTE: But what if we want to compute barycentric coordinates on a sub-entity and still want facet ordering? | |
| # Then we need to pass facet_ordering=True even when factor_sd > 1 | |
| factor_bary_coords = factor.compute_barycentric_coordinates(points[..., s], entity, rescale) | |
| result.append(factor_bary_coords) | |
| result = [factor.compute_barycentric_coordinates(points[..., s], entity, rescale, facet_ordering=True) | |
| for factor, s in zip(self.cells, point_slices)] |
| facets = [top[entity_dim-1][f] for f in facet_ids] # facet as a tuple of vertex indices that form it | ||
|
|
||
| perm = [facets.index(tuple(sorted(set(global_indices) - {v}))) for v in global_indices] | ||
| indices = [indices[p] for p in perm] |
There was a problem hiding this comment.
| indices = [indices[p] for p in perm] | |
| indices = [indices[p] for p in numpy.argsort(perm)] |
| return unflattening_map | ||
|
|
||
|
|
||
| def compute_facet_permutation(unflattening_map, product): |
There was a problem hiding this comment.
We should get rid of this function if not used anymore
| def __le__(self, other): | ||
| return self.product <= other | ||
|
|
||
| def compute_barycentric_coordinates(self, points, entity=None, rescale=False): |
There was a problem hiding this comment.
| def compute_barycentric_coordinates(self, points, entity=None, rescale=False): | |
| def compute_barycentric_coordinates(self, points, entity=None, rescale=False, facet_ordering=True): |
|
|
||
| Returns | ||
| ------- | ||
| List of numpy.ndarray or GEM.ComponentTensor |
There was a problem hiding this comment.
Not quite true, so far we return a single numpy.ndarray
| # get a subcell containing the entity and the restriction indices of the entity | ||
| indices = slice(None) | ||
| subcomplex = top[entity_dim][entity_id] | ||
| # indices = slice(None) |
There was a problem hiding this comment.
| # indices = slice(None) | |
| # get a subcell containing the entity and the restriction indices of the entity |
| (tetrahedron, [0.25, 0.25, 0.25]), | ||
| (quadrilateral, [0.25, 0.5]), | ||
| (hexahedron, [0.25, 0.5, 0.25]),]) | ||
| def test_bary_coords_gem(cell, point): |
There was a problem hiding this comment.
This test could pass at the moment, we just need to cast back and forth between gem and numpy
| (hexahedron, [0.3, 0.4, 1.0]),]) | ||
| def test_hypercube_bary_coords_are_in_facet_order(cell, point, epsilon=1e-12): | ||
| """Test that the barycentric coordinates computed on hypercubes are listed in facet order.""" | ||
| point = np.asarray(point) |
There was a problem hiding this comment.
we should support a single or multiple points as tuple or numpy.array
| coords = gem.Variable('X', (sd,)) | ||
| bindings = {coords: point} | ||
|
|
||
| bary_gem = cell.compute_barycentric_coordinates(coords) |
There was a problem hiding this comment.
| bary_gem = cell.compute_barycentric_coordinates(coords) | |
| np_coords = tuple(coords[i] for i in range(sd)) | |
| bary_gem = gem.as_gem(cell.compute_barycentric_coordinates(np_coords)) |
| factor_bary_coords = factor.compute_barycentric_coordinates(points[..., s], entity, rescale) | ||
| result.append(factor_bary_coords) | ||
|
|
||
| flat_result = numpy.concatenate(result) |
There was a problem hiding this comment.
| flat_result = numpy.concatenate(result) | |
| flat_result = numpy.concatenate(result, axis=-1) |
| if isinstance(points, numpy.ndarray) and len(points) == 0: | ||
| return points | ||
|
|
There was a problem hiding this comment.
| if isinstance(points, numpy.ndarray) and len(points) == 0: | |
| return points |
| if isinstance(points, numpy.ndarray) and len(points) == 0: | ||
| return points | ||
|
|
||
| tp_bary_coords = self.product.compute_factor_barycentric_coordinates(points, entity, rescale) |
There was a problem hiding this comment.
| tp_bary_coords = self.product.compute_factor_barycentric_coordinates(points, entity, rescale) | |
| return self.product.compute_factor_barycentric_coordinates(points, entity, rescale) |
This is the first part of PR #245 concerning changes to FIAT only. In particular, I have extended FIAT to enable the computation of barycentric coordinates on tensor product cells.
The main distinction lies in how barycentric coordinates are represented in simplicies vs tensor product cells. For simplicies, there is one barycentric coordinate per vertex so the default ordering of these coordinates follows the vertex ordering. For tensor product cells, there is one barycentric coordinate per facet so the natural ordering is that of facets.
First, to enable facet ordering in simplicies, I have added a facet ordering permutation in
SimplicialComplex.compute_barycentric_coordinates. Then, since barycentric coordinates in tensor product cells are computed recursively on each factor, having facet ordering on the simplicies that form each factor ensure that we obtain facet ordering on the tensor-product cell as a whole.