|
24 | 24 | )
|
25 | 25 | from dolfinx.mesh import (
|
26 | 26 | CellType,
|
| 27 | + compute_midpoints, |
27 | 28 | create_box,
|
28 | 29 | create_unit_cube,
|
29 | 30 | create_unit_interval,
|
@@ -501,56 +502,107 @@ def test_surface_bbtree_collision(dtype):
|
501 | 502 |
|
502 | 503 |
|
503 | 504 | @pytest.mark.parametrize("dim", [2, 3])
|
| 505 | +@pytest.mark.parametrize("affine", [True, False]) |
504 | 506 | @pytest.mark.parametrize("dtype", [np.float32, np.float64])
|
505 |
| -def test_determine_point_ownership(dim, dtype): |
| 507 | +def test_determine_point_ownership(dim, affine, dtype): |
506 | 508 | """Find point owners (ranks and cells) using bounding box trees + global communication
|
507 | 509 | and compare to point ownership data results."""
|
508 | 510 | comm = MPI.COMM_WORLD
|
509 | 511 | rank = comm.Get_rank()
|
| 512 | + mpi_dtype = MPI.DOUBLE if dtype == np.float64 else MPI.FLOAT |
510 | 513 |
|
511 | 514 | tdim = dim
|
512 | 515 | num_cells_side = 4
|
513 |
| - h = dtype(1.0 / num_cells_side) |
514 | 516 | if tdim == 2:
|
515 |
| - mesh = create_unit_square( |
516 |
| - MPI.COMM_WORLD, num_cells_side, num_cells_side, CellType.quadrilateral, dtype=dtype |
517 |
| - ) |
| 517 | + ct = CellType.triangle if affine else CellType.quadrilateral |
| 518 | + mesh = create_unit_square(MPI.COMM_WORLD, num_cells_side, num_cells_side, ct, dtype=dtype) |
518 | 519 | else:
|
| 520 | + ct = CellType.tetrahedron if affine else CellType.hexahedron |
519 | 521 | mesh = create_unit_cube(
|
520 | 522 | MPI.COMM_WORLD,
|
521 | 523 | num_cells_side,
|
522 | 524 | num_cells_side,
|
523 | 525 | num_cells_side,
|
524 |
| - CellType.hexahedron, |
| 526 | + ct, |
525 | 527 | dtype=dtype,
|
526 | 528 | )
|
527 | 529 | cell_map = mesh.topology.index_map(tdim)
|
528 | 530 |
|
529 | 531 | tree = bb_tree(mesh, mesh.topology.dim, np.arange(cell_map.size_local))
|
530 |
| - num_global_cells = num_cells_side**dim |
| 532 | + num_global_cells = num_cells_side**tdim |
| 533 | + if affine: |
| 534 | + num_global_cells *= 2 * (3 ** (tdim - 2)) |
| 535 | + local_midpoints = compute_midpoints( |
| 536 | + mesh, tdim, np.arange(mesh.topology.index_map(tdim).size_local) |
| 537 | + ) |
| 538 | + midpoints_per_rank = np.zeros(comm.size, dtype=np.int32) |
| 539 | + midpoints_offsets = np.zeros(comm.size, dtype=np.int32) |
| 540 | + comm.Allgather(np.array([local_midpoints.shape[0]], dtype=np.int32), midpoints_per_rank) |
| 541 | + midpoints_offsets[1:] = np.cumsum(midpoints_per_rank[:-1]) |
531 | 542 | all_midpoints = np.zeros((num_global_cells, 3), dtype=dtype)
|
532 |
| - counter = 0 |
533 |
| - Xs = [(i + 0.5) * h for i in range(num_cells_side)] |
534 |
| - Ys = Xs |
535 |
| - Zs = [0.0] if dim == 2 else Xs |
536 |
| - for x in Xs: |
537 |
| - for y in Ys: |
538 |
| - for z in Zs: |
539 |
| - all_midpoints[counter, :] = np.array([x, y, z], dtype=dtype) |
540 |
| - counter += 1 |
541 |
| - # Since ghost cells are left out and the points considered are midpoints |
542 |
| - # of cells, they are only contained in a single process / cell |
543 |
| - # Moreover, the bounding boxes of the elements correspond to the elements, |
544 |
| - # hence the tree collisions are the exact collisions |
| 543 | + comm.Allgatherv( |
| 544 | + local_midpoints, [all_midpoints, midpoints_per_rank * 3, midpoints_offsets * 3, mpi_dtype] |
| 545 | + ) |
| 546 | + # Find potential owner cells |
545 | 547 | tree_col = compute_collisions_points(tree, all_midpoints)
|
| 548 | + |
| 549 | + mesh.topology.create_connectivity(tdim - 1, 0) |
| 550 | + mesh.topology.create_connectivity(0, tdim) |
| 551 | + cfc = mesh.topology.connectivity(tdim, tdim - 1) |
| 552 | + fpc = mesh.topology.connectivity(tdim - 1, 0) |
| 553 | + |
| 554 | + # Narrow it down to a single owner cell |
| 555 | + def is_inside(mesh, icell, point): |
| 556 | + fdim = tdim - 1 |
| 557 | + is_inside = True |
| 558 | + cpoints = mesh.geometry.x[mesh.geometry.dofmap[icell, :]] # cell points |
| 559 | + ccentroid = np.average(cpoints, axis=0) # cell centroid |
| 560 | + for ifacet in cfc.links(icell): |
| 561 | + fpoints_indices = _cpp.mesh.entities_to_geometry( |
| 562 | + mesh._cpp_object, |
| 563 | + 0, |
| 564 | + fpc.links(ifacet), |
| 565 | + False, |
| 566 | + ) |
| 567 | + fpoints_indices = fpoints_indices.reshape(fpoints_indices.size) |
| 568 | + fpoints = mesh.geometry.x[fpoints_indices] |
| 569 | + fcentroid = np.average(fpoints, axis=0) # facet centroid |
| 570 | + # Compute facet normal pointing to outside of owner cell |
| 571 | + normal = np.zeros(3, dtype=dtype) |
| 572 | + facet_vector1 = fpoints[1, :] - fpoints[0, :] |
| 573 | + if fdim == 1: |
| 574 | + normal[0] = -facet_vector1[1] |
| 575 | + normal[1] = +facet_vector1[0] |
| 576 | + elif fdim == 2: |
| 577 | + facet_vector2 = fpoints[2, :] - fpoints[0, :] |
| 578 | + normal = np.cross(facet_vector1, facet_vector2) |
| 579 | + else: |
| 580 | + raise ValueError("Unexpected facet dimension.") |
| 581 | + normal /= np.linalg.norm(normal) |
| 582 | + # Re-align if pointing to inside the parent cell |
| 583 | + normal = -normal if (np.dot((ccentroid - fcentroid), normal) > 0) else normal |
| 584 | + # Test the point |
| 585 | + signed_distance = np.dot((point - fcentroid), normal) |
| 586 | + if signed_distance > 1e-9: |
| 587 | + is_inside = False |
| 588 | + break |
| 589 | + return is_inside |
| 590 | + |
546 | 591 | processwise_owners = np.zeros(2 * num_global_cells, dtype=np.int32)
|
547 | 592 | owners = np.empty_like(processwise_owners)
|
548 | 593 | for ipoint in range(num_global_cells):
|
549 |
| - cell = tree_col.links(ipoint) |
550 |
| - if len(cell) > 0: |
| 594 | + potential_owners = tree_col.links(ipoint) |
| 595 | + owner_cells = [] |
| 596 | + for cell in potential_owners: |
| 597 | + if is_inside(mesh, cell, all_midpoints[ipoint, :]): |
| 598 | + owner_cells.append(cell) |
| 599 | + if owner_cells: |
| 600 | + assert len(owner_cells) == 1 |
551 | 601 | processwise_owners[2 * ipoint] = rank
|
552 |
| - processwise_owners[2 * ipoint + 1] = cell[0] |
| 602 | + processwise_owners[2 * ipoint + 1] = owner_cells[0] |
553 | 603 |
|
| 604 | + # Since ghost cells are left out and the points considered are midpoints |
| 605 | + # of cells, they are only contained in a single process / cell |
554 | 606 | # The value at a given index is null if it doesn't correspond
|
555 | 607 | # to the current process.
|
556 | 608 | # We can sum the processwise arrays to obtain a global array
|
@@ -591,7 +643,7 @@ def check_po(po: PointOwnershipData, src_points, ownership_data, global_dest_own
|
591 | 643 | (iglobal, processor, _) = data
|
592 | 644 | if processor == rank:
|
593 | 645 | found = False
|
594 |
| - point = np.array(point, dtype) |
| 646 | + point = np.array(point, dtype=dtype) |
595 | 647 | for jpoint in dest_points_indices:
|
596 | 648 | found = np.allclose(point, dest_points[jpoint])
|
597 | 649 | if found:
|
@@ -630,26 +682,33 @@ def compute_global_owners(N, start, stop):
|
630 | 682 | # All cells
|
631 | 683 | points, start, stop = set_local_range(all_midpoints)
|
632 | 684 | owners = compute_global_owners(np.int64(all_midpoints.shape[0]), start, stop)
|
633 |
| - all_cells = np.arange(cell_map.size_local, dtype=np.int32) |
634 |
| - po = determine_point_ownership(mesh, points, all_cells) |
| 685 | + all_cells = np.arange(cell_map.size_local, dtype=dtype) |
| 686 | + po = determine_point_ownership(mesh, points, 0.0, all_cells) |
635 | 687 |
|
636 | 688 | check_po(po, points, ownership_data, owners)
|
637 | 689 |
|
638 | 690 | # Left half
|
639 |
| - num_left_cells = np.rint((num_cells_side**dim) / 2).astype(np.int32) |
640 |
| - left_midpoints = all_midpoints[np.arange(num_left_cells), :] |
| 691 | + num_left_cells = np.rint(num_global_cells / 2).astype(np.int32) |
| 692 | + left_midpoints = np.zeros((num_left_cells, 3), dtype=dtype) |
| 693 | + counter = 0 |
| 694 | + indices_left = [] |
| 695 | + for ipoint in range(num_global_cells): |
| 696 | + if all_midpoints[ipoint, 0] <= 0.5: |
| 697 | + left_midpoints[counter] = all_midpoints[ipoint] |
| 698 | + indices_left.append(ipoint) |
| 699 | + counter += 1 |
641 | 700 | points, start, stop = set_local_range(left_midpoints)
|
642 | 701 | owners = compute_global_owners(np.int64(all_midpoints.shape[0]), start, stop)
|
643 | 702 | left_cells = locate_entities(mesh, tdim, lambda x: x[0] <= 0.5)
|
644 | 703 | left_cells = np.array(
|
645 | 704 | [cell for cell in left_cells if cell < cell_map.size_local], dtype=np.int32
|
646 | 705 | ) # Filter out ghost cells
|
647 |
| - lpo = determine_point_ownership(mesh, points, left_cells) |
| 706 | + lpo = determine_point_ownership(mesh, points, 0.0, left_cells) |
648 | 707 |
|
649 | 708 | left_ownership_data = {}
|
650 |
| - for ipoint in range(num_left_cells): |
| 709 | + for idx, ipoint in enumerate(indices_left): |
651 | 710 | left_ownership_data[tuple(all_midpoints[ipoint])] = (
|
652 |
| - ipoint, |
| 711 | + idx, |
653 | 712 | owner_ranks[ipoint],
|
654 | 713 | owner_cells[ipoint],
|
655 | 714 | )
|
|
0 commit comments