6
6
import finat .ufl
7
7
import FIAT
8
8
import weakref
9
+ from typing import Tuple
9
10
from collections import OrderedDict , defaultdict
10
11
from collections .abc import Sequence
11
12
from ufl .classes import ReferenceGrad
@@ -2289,6 +2290,7 @@ def __init__(self, coordinates):
2289
2290
# submesh
2290
2291
self .submesh_parent = None
2291
2292
2293
+ self ._bounding_box_coords = None
2292
2294
self ._spatial_index = None
2293
2295
self ._saved_coordinate_dat_version = coordinates .dat .dat_version
2294
2296
@@ -2457,54 +2459,71 @@ def clear_spatial_index(self):
2457
2459
the coordinate field)."""
2458
2460
self ._spatial_index = None
2459
2461
2460
- @property
2461
- def spatial_index (self ):
2462
- """Spatial index to quickly find which cell contains a given point .
2462
+ @utils . cached_property
2463
+ def bounding_box_coords (self ) -> Tuple [ np . ndarray , np . ndarray ] | None :
2464
+ """Calculates bounding boxes for spatial indexing .
2463
2465
2464
- Notes
2465
- -----
2466
+ Returns
2467
+ -------
2468
+ Tuple of arrays of shape (num_cells, gdim) containing
2469
+ the minimum and maximum coordinates of each cell's bounding box.
2466
2470
2467
- If this mesh has a :attr:`tolerance` property, which
2468
- should be a float, this tolerance is added to the extrama of the
2469
- spatial index so that points just outside the mesh, within tolerance,
2470
- can be found.
2471
+ None if the geometric dimension is 1, since libspatialindex
2472
+ does not support 1D.
2471
2473
2474
+ Notes
2475
+ -----
2476
+ If we have a higher-order (bendy) mesh we project the mesh coordinates into
2477
+ a Bernstein finite element space. Functions on a Bernstein element are
2478
+ Bezier curves and are completely contained in the convex hull of the mesh nodes.
2479
+ Hence the bounding box will contain the entire element.
2472
2480
"""
2473
2481
from firedrake import function , functionspace
2474
2482
from firedrake .parloops import par_loop , READ , MIN , MAX
2475
2483
2476
- if (
2477
- self ._spatial_index
2478
- and self .coordinates .dat .dat_version == self ._saved_coordinate_dat_version
2479
- ):
2480
- return self ._spatial_index
2481
-
2482
2484
gdim = self .geometric_dimension ()
2483
2485
if gdim <= 1 :
2484
2486
info_red ("libspatialindex does not support 1-dimension, falling back on brute force." )
2485
2487
return None
2486
2488
2489
+ coord_element = self .ufl_coordinate_element ()
2490
+ coord_degree = coord_element .degree ()
2491
+ if ufl .cell .simplex (self .topological_dimension ()) != self .ufl_cell ():
2492
+ # Non-simplex element, e.g. quad or tensor product
2493
+ mesh = self
2494
+ elif coord_degree == 1 :
2495
+ mesh = self
2496
+ elif coord_element .family () == "Bernstein" :
2497
+ # Already have Bernstein coordinates, no need to project
2498
+ mesh = self
2499
+ else :
2500
+ # For bendy meshes we project the coordinate function onto Bernstein
2501
+ bernstein_fs = functionspace .VectorFunctionSpace (self , "Bernstein" , coord_degree )
2502
+ f = function .Function (bernstein_fs )
2503
+ f .interpolate (self .coordinates )
2504
+ mesh = Mesh (f )
2505
+
2487
2506
# Calculate the bounding boxes for all cells by running a kernel
2488
- V = functionspace .VectorFunctionSpace (self , "DG" , 0 , dim = gdim )
2507
+ V = functionspace .VectorFunctionSpace (mesh , "DG" , 0 , dim = gdim )
2489
2508
coords_min = function .Function (V , dtype = RealType )
2490
2509
coords_max = function .Function (V , dtype = RealType )
2491
2510
2492
2511
coords_min .dat .data .fill (np .inf )
2493
2512
coords_max .dat .data .fill (- np .inf )
2494
2513
2495
2514
if utils .complex_mode :
2496
- if not np .allclose (self .coordinates .dat .data_ro .imag , 0 ):
2515
+ if not np .allclose (mesh .coordinates .dat .data_ro .imag , 0 ):
2497
2516
raise ValueError ("Coordinate field has non-zero imaginary part" )
2498
- coords = function .Function (self .coordinates .function_space (),
2499
- val = self .coordinates .dat .data_ro_with_halos .real .copy (),
2517
+ coords = function .Function (mesh .coordinates .function_space (),
2518
+ val = mesh .coordinates .dat .data_ro_with_halos .real .copy (),
2500
2519
dtype = RealType )
2501
2520
else :
2502
- coords = self .coordinates
2521
+ coords = mesh .coordinates
2503
2522
2504
- cell_node_list = self .coordinates .function_space ().cell_node_list
2523
+ cell_node_list = mesh .coordinates .function_space ().cell_node_list
2505
2524
_ , nodes_per_cell = cell_node_list .shape
2506
2525
2507
- domain = "{{[d, i]: 0 <= d < {0 } and 0 <= i < {1 }}}" . format ( gdim , nodes_per_cell )
2526
+ domain = f "{{[d, i]: 0 <= d < { gdim } and 0 <= i < { nodes_per_cell } }}"
2508
2527
instructions = """
2509
2528
for d, i
2510
2529
f_min[0, d] = fmin(f_min[0, d], f[i, d])
@@ -2518,21 +2537,51 @@ def spatial_index(self):
2518
2537
2519
2538
# Reorder bounding boxes according to the cell indices we use
2520
2539
column_list = V .cell_node_list .reshape (- 1 )
2521
- coords_min = self ._order_data_by_cell_index (column_list , coords_min .dat .data_ro_with_halos )
2522
- coords_max = self ._order_data_by_cell_index (column_list , coords_max .dat .data_ro_with_halos )
2540
+ coords_min = mesh ._order_data_by_cell_index (column_list , coords_min .dat .data_ro_with_halos )
2541
+ coords_max = mesh ._order_data_by_cell_index (column_list , coords_max .dat .data_ro_with_halos )
2542
+
2543
+ return coords_min , coords_max
2544
+
2545
+ @property
2546
+ def spatial_index (self ):
2547
+ """Builds spatial index from bounding box coordinates, expanding
2548
+ the bounding box by the mesh tolerance.
2549
+
2550
+ Returns
2551
+ -------
2552
+ :class:`~.spatialindex.SpatialIndex` or None if the mesh is
2553
+ one-dimensional.
2554
+
2555
+ Notes
2556
+ -----
2557
+ If this mesh has a :attr:`tolerance` property, which
2558
+ should be a float, this tolerance is added to the extrema of the
2559
+ spatial index so that points just outside the mesh, within tolerance,
2560
+ can be found.
2523
2561
2562
+ """
2563
+ if self .coordinates .dat .dat_version != self ._saved_coordinate_dat_version :
2564
+ if "bounding_box_coords" in self .__dict__ :
2565
+ del self .bounding_box_coords
2566
+ else :
2567
+ if self ._spatial_index :
2568
+ return self ._spatial_index
2524
2569
# Change min and max to refer to an n-hypercube, where n is the
2525
2570
# geometric dimension of the mesh, centred on the midpoint of the
2526
2571
# bounding box. Its side length is the L1 diameter of the bounding box.
2527
2572
# This aids point evaluation on immersed manifolds and other cases
2528
2573
# where points may be just off the mesh but should be evaluated.
2529
2574
# TODO: This is perhaps unnecessary when we aren't in these special
2530
2575
# cases.
2531
-
2532
2576
# We also push max and min out so we can find points on the boundary
2533
2577
# within the mesh tolerance.
2534
2578
# NOTE: getattr doesn't work here due to the inheritance games that are
2535
2579
# going on in getattr.
2580
+ if self .bounding_box_coords is None :
2581
+ # This happens in 1D meshes
2582
+ return None
2583
+ else :
2584
+ coords_min , coords_max = self .bounding_box_coords
2536
2585
tolerance = self .tolerance if hasattr (self , "tolerance" ) else 0.0
2537
2586
coords_mid = (coords_max + coords_min )/ 2
2538
2587
d = np .max (coords_max - coords_min , axis = 1 )[:, None ]
@@ -3360,11 +3409,13 @@ def VertexOnlyMesh(mesh, vertexcoords, reorder=None, missing_points_behaviour='e
3360
3409
_ , pdim = vertexcoords .shape
3361
3410
if not np .isclose (np .sum (abs (vertexcoords .imag )), 0 ):
3362
3411
raise ValueError ("Point coordinates must have zero imaginary part" )
3363
- # Bendy meshes require a smarter bounding box algorithm at partition and
3364
- # (especially) cell level. Projecting coordinates to Bernstein may be
3365
- # sufficient.
3366
- if np .any (np .asarray (mesh .coordinates .function_space ().ufl_element ().degree ()) > 1 ):
3367
- raise NotImplementedError ("Only straight edged meshes are supported" )
3412
+ if (
3413
+ ufl .cell .simplex (mesh .topological_dimension ()) != mesh .ufl_cell ()
3414
+ and np .any (np .asarray (mesh .ufl_coordinate_element ().degree ()) > 1 )
3415
+ ):
3416
+ raise NotImplementedError (
3417
+ "Cannot yet immerse a VertexOnlyMesh in non-simplicial higher-order meshes."
3418
+ )
3368
3419
# Currently we take responsibility for locating the mesh cells in which the
3369
3420
# vertices lie.
3370
3421
#
0 commit comments