diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 2ee2af4..b43c30c 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -1,52 +1,63 @@ cmake_minimum_required(VERSION 3.21) -# ------------------------------------------------------------------------------ -# Set project name and version number project(CutCells VERSION "0.2.0" LANGUAGES CXX C) -# Use C++20 set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -# Options option(BUILD_SHARED_LIBS "Build CutCells with shared libraries." ON) -#add_feature_info(BUILD_SHARED_LIBS BUILD_SHARED_LIBS "Build cutfemx with shared libraries.") -include(FeatureSummary) +include(GNUInstallDirs) +include(CheckIPOSupported) -# Source files add_subdirectory(src) -set_target_properties(cutcells PROPERTIES PRIVATE_HEADER "${HEADERS}") +find_package(OpenMP QUIET COMPONENTS CXX) +if(OpenMP_CXX_FOUND AND TARGET OpenMP::OpenMP_CXX) + target_link_libraries(cutcells PUBLIC OpenMP::OpenMP_CXX) +endif() + +check_ipo_supported(RESULT CUTCELLS_IPO_SUPPORTED OUTPUT CUTCELLS_IPO_ERROR) +if(CUTCELLS_IPO_SUPPORTED) + set_property(TARGET cutcells PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) +endif() + target_include_directories(cutcells PUBLIC - $ - "$") + $ + $) + +install(FILES ${HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cutcells + COMPONENT Development) -install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cutcells/${DIR} -COMPONENT Development) +# Install generated headers produced at build time (tables, etc.) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/generated + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cutcells + COMPONENT Development + FILES_MATCHING PATTERN "*.h") # Install the cutcells library install(TARGETS cutcells EXPORT CutCellsTargets - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - PRIVATE_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/src RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT RuntimeExecutables LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT RuntimeLibraries ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development) -# Configure CMake helpers include(CMakePackageConfigHelpers) -write_basic_package_version_file(CutCellsConfigVersion.cmake VERSION ${PACKAGE_VERSION} +write_basic_package_version_file(CutCellsConfigVersion.cmake + VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) -configure_package_config_file(CutCellsConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfig.cmake +configure_package_config_file(CutCellsConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cutcells) -# Install CMake files -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfigVersion.cmake +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/CutCellsConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cutcells COMPONENT Development) -install(EXPORT CutCellsTargets FILE CutCellsTargets.cmake NAMESPACE CUTCELLS:: +install(EXPORT CutCellsTargets + FILE CutCellsTargets.cmake + NAMESPACE CUTCELLS:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cutcells) - - diff --git a/cpp/demo/cut_interval/main.cpp b/cpp/demo/cut_interval/main.cpp index bbcc628..91181b5 100644 --- a/cpp/demo/cut_interval/main.cpp +++ b/cpp/demo/cut_interval/main.cpp @@ -32,17 +32,17 @@ int main() cell::CutCell cut_cell; cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi=0", cut_cell); std::string fname = "interface.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi<0", cut_cell); fname = "interior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "interior.vtu"; io::write_vtk(fname,cut_cell); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi>0", cut_cell, false); fname = "exterior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "exterior.vtu"; io::write_vtk(fname,cut_cell); } diff --git a/cpp/demo/cut_tetrahedron/main.cpp b/cpp/demo/cut_tetrahedron/main.cpp index 3f72559..4d38817 100644 --- a/cpp/demo/cut_tetrahedron/main.cpp +++ b/cpp/demo/cut_tetrahedron/main.cpp @@ -35,19 +35,19 @@ int main() cell::CutCell cut_cell; cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi=0", cut_cell, triangulate); std::string fname = "interface.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "interface.vtu"; io::write_vtk(fname,cut_cell); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi<0", cut_cell, triangulate); fname = "interior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "interior.vtu"; io::write_vtk(fname,cut_cell); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi>0", cut_cell, triangulate); fname = "exterior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "exterior.vtu"; io::write_vtk(fname,cut_cell); } diff --git a/cpp/demo/cut_triangle/main.cpp b/cpp/demo/cut_triangle/main.cpp index 2313347..4c07542 100644 --- a/cpp/demo/cut_triangle/main.cpp +++ b/cpp/demo/cut_triangle/main.cpp @@ -32,17 +32,17 @@ int main() cell::CutCell cut_cell; cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi=0", cut_cell); std::string fname = "interface.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi<0", cut_cell); fname = "interior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "interior.vtu"; io::write_vtk(fname,cut_cell); cell::cut(cell_type, vertex_coordinates, gdim, ls_values, "phi>0", cut_cell, false); fname = "exterior.tex"; - io::write_tikz(fname,cut_cell._vertex_coords,cut_cell._connectivity,vertex_coordinates,bg_elements,ls_values,gdim); + io::write_tikz(fname,cut_cell,vertex_coordinates,bg_elements,ls_values,gdim); fname = "exterior.vtu"; io::write_vtk(fname,cut_cell); } diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 8885064..3b6f073 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -1,12 +1,8 @@ -# ------------------------------------------------------------------------------ include(GNUInstallDirs) -# ------------------------------------------------------------------------------ -# Declare the library (target) add_library(cutcells) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in version.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Make headers in this directory discoverable for generated table headers # (e.g. generated/*_tables.h includes "cell_types.h"). @@ -18,16 +14,33 @@ set(HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/cell_types.h ${CMAKE_CURRENT_SOURCE_DIR}/cell_flags.h ${CMAKE_CURRENT_SOURCE_DIR}/cell_subdivision.h + ${CMAKE_CURRENT_SOURCE_DIR}/cell_topology.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_triangle.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_interval.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_quadrilateral.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_hexahedron.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_prism.h + ${CMAKE_CURRENT_SOURCE_DIR}/quad_midpoint_split.h + ${CMAKE_CURRENT_SOURCE_DIR}/prism_midpoint_split.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_pyramid.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_tetrahedron.h ${CMAKE_CURRENT_SOURCE_DIR}/triangulation.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_cell.h ${CMAKE_CURRENT_SOURCE_DIR}/cut_mesh.h + ${CMAKE_CURRENT_SOURCE_DIR}/mapping.h + ${CMAKE_CURRENT_SOURCE_DIR}/bernstein.h + ${CMAKE_CURRENT_SOURCE_DIR}/level_set.h + ${CMAKE_CURRENT_SOURCE_DIR}/level_set_cell.h + ${CMAKE_CURRENT_SOURCE_DIR}/mesh_view.h + ${CMAKE_CURRENT_SOURCE_DIR}/adapt_cell.h + ${CMAKE_CURRENT_SOURCE_DIR}/ho_cut_mesh.h + ${CMAKE_CURRENT_SOURCE_DIR}/ho_mesh_part_output.h + ${CMAKE_CURRENT_SOURCE_DIR}/selection_expr.h + ${CMAKE_CURRENT_SOURCE_DIR}/edge_certification.h + ${CMAKE_CURRENT_SOURCE_DIR}/cell_certification.h + ${CMAKE_CURRENT_SOURCE_DIR}/refine_cell.h + ${CMAKE_CURRENT_SOURCE_DIR}/quadrature.h + ${CMAKE_CURRENT_SOURCE_DIR}/quadrature_tables.h ${CMAKE_CURRENT_SOURCE_DIR}/span_math.h ${CMAKE_CURRENT_SOURCE_DIR}/utils.h ${CMAKE_CURRENT_SOURCE_DIR}/write_tikz.h @@ -40,13 +53,26 @@ target_sources(cutcells PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cut_quadrilateral.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_hexahedron.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_prism.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/quad_midpoint_split.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/prism_midpoint_split.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_pyramid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_tetrahedron.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_cell.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cut_mesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/bernstein.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/level_set.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/level_set_cell.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adapt_cell.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ho_cut_mesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ho_mesh_part_output.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/selection_expr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/edge_certification.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cell_certification.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/refine_cell.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/quadrature.cpp ${CMAKE_CURRENT_SOURCE_DIR}/write_vtk.cpp - ) - - +target_compile_options(cutcells PRIVATE + $<$:-O3>) diff --git a/cpp/src/adapt_cell.cpp b/cpp/src/adapt_cell.cpp new file mode 100644 index 0000000..5be42df --- /dev/null +++ b/cpp/src/adapt_cell.cpp @@ -0,0 +1,660 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "adapt_cell.h" +#include "cell_topology.h" +#include "reference_cell.h" + +#include +#include +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +template +void clear_entity_host_provenance(AdaptCell& ac, int dim) +{ + ac.entity_host_cell_id[dim].clear(); + ac.entity_host_cell_type[dim].clear(); + ac.entity_host_face_id[dim].clear(); + ac.entity_source_level_set[dim].clear(); + ac.entity_host_cell_vertices[dim].offsets.clear(); + ac.entity_host_cell_vertices[dim].indices.clear(); + ac.entity_host_cell_vertices[dim].offsets.push_back(std::int32_t(0)); +} + +template +std::span host_vertices_for_entity(const AdaptCell& ac, + int dim, + int entity_id) +{ + if (dim >= 0 && dim < AdaptCell::max_dim + && entity_id >= 0 + && entity_id < ac.entity_host_cell_vertices[dim].size()) + { + return ac.entity_host_cell_vertices[dim][static_cast(entity_id)]; + } + return {}; +} + +template +void append_entity_host_provenance(AdaptCell& ac, + int dim, + int host_cell_id, + cell::type host_cell_type, + int host_face_id, + int source_level_set, + std::span host_vertices) +{ + ac.entity_host_cell_id[dim].push_back(static_cast(host_cell_id)); + ac.entity_host_cell_type[dim].push_back(host_cell_type); + ac.entity_host_face_id[dim].push_back(static_cast(host_face_id)); + ac.entity_source_level_set[dim].push_back(static_cast(source_level_set)); + for (const auto v : host_vertices) + ac.entity_host_cell_vertices[dim].indices.push_back(v); + ac.entity_host_cell_vertices[dim].offsets.push_back( + static_cast(ac.entity_host_cell_vertices[dim].indices.size())); +} + +template +T squared_distance_to_segment(std::span p, + std::span a, + std::span b, + int dim) +{ + T ab2 = T(0); + T ap_ab = T(0); + for (int d = 0; d < dim; ++d) + { + const T ab = b[static_cast(d)] - a[static_cast(d)]; + const T ap = p[static_cast(d)] - a[static_cast(d)]; + ab2 += ab * ab; + ap_ab += ap * ab; + } + const T t = (ab2 > T(0)) ? std::clamp(ap_ab / ab2, T(0), T(1)) : T(0); + T dist2 = T(0); + for (int d = 0; d < dim; ++d) + { + const T x = a[static_cast(d)] + + t * (b[static_cast(d)] - a[static_cast(d)]); + const T r = p[static_cast(d)] - x; + dist2 += r * r; + } + return dist2; +} + +template +bool point_in_face_span(std::span p, + const std::vector& ref_vertices, + int tdim, + std::span face_vertices, + T tol) +{ + if (tdim != 3 || face_vertices.size() < 3) + return false; + + const T* a = ref_vertices.data() + static_cast(face_vertices[0] * tdim); + const T* b = ref_vertices.data() + static_cast(face_vertices[1] * tdim); + const T* c = ref_vertices.data() + static_cast(face_vertices[2] * tdim); + std::array u = {}; + std::array v = {}; + std::array w = {}; + for (int d = 0; d < 3; ++d) + { + u[static_cast(d)] = b[d] - a[d]; + v[static_cast(d)] = c[d] - a[d]; + w[static_cast(d)] = p[static_cast(d)] - a[d]; + } + const std::array n = { + u[1] * v[2] - u[2] * v[1], + u[2] * v[0] - u[0] * v[2], + u[0] * v[1] - u[1] * v[0]}; + const T n2 = n[0] * n[0] + n[1] * n[1] + n[2] * n[2]; + if (n2 <= tol * tol) + return false; + const T signed_dist = + (n[0] * w[0] + n[1] * w[1] + n[2] * w[2]) / std::sqrt(n2); + return std::fabs(signed_dist) <= tol; +} + +template +std::pair +infer_zero_entity_parent_host(const AdaptCell& ac, + std::span entity_vertices) +{ + const T tol = T(256) * std::numeric_limits::epsilon(); + const int tdim = ac.tdim; + const auto ref_vertices = cell::reference_vertices(ac.parent_cell_type); + + if (tdim >= 1) + { + auto parent_edges = cell::edges(ac.parent_cell_type); + for (int e = 0; e < static_cast(parent_edges.size()); ++e) + { + const auto edge = parent_edges[static_cast(e)]; + std::span a( + ref_vertices.data() + static_cast(edge[0] * tdim), + static_cast(tdim)); + std::span b( + ref_vertices.data() + static_cast(edge[1] * tdim), + static_cast(tdim)); + + bool all_on_edge = true; + for (const auto v : entity_vertices) + { + std::span p( + ac.vertex_coords.data() + static_cast(v * tdim), + static_cast(tdim)); + if (squared_distance_to_segment(p, a, b, tdim) > tol * tol) + { + all_on_edge = false; + break; + } + } + if (all_on_edge) + return {std::int8_t(1), std::int32_t(e)}; + } + } + + if (tdim == 3) + { + const int nfaces = cell::num_faces(ac.parent_cell_type); + for (int f = 0; f < nfaces; ++f) + { + auto face_vertices = cell::face_vertices(ac.parent_cell_type, f); + bool all_on_face = true; + for (const auto v : entity_vertices) + { + std::span p( + ac.vertex_coords.data() + static_cast(v * tdim), + static_cast(tdim)); + if (!point_in_face_span(p, ref_vertices, tdim, face_vertices, tol)) + { + all_on_face = false; + break; + } + } + if (all_on_face) + return {std::int8_t(2), std::int32_t(f)}; + } + } + + return {static_cast(tdim), static_cast(ac.parent_cell_id)}; +} + +} // namespace + +template +void build_faces(AdaptCell& ac) +{ + if (ac.tdim < 3) + return; + + // Clear existing 2D entity pool. + ac.entity_types[2].clear(); + ac.entity_to_vertex[2].offsets.clear(); + ac.entity_to_vertex[2].indices.clear(); + ac.entity_to_vertex[2].offsets.push_back(std::int32_t(0)); + clear_entity_host_provenance(ac, 2); + + // Invalidate face cert tag storage: face count is about to change and + // faces may be reordered, so all old tags are stale. + ac.face_cert_tag.clear(); + ac.face_cert_tag_num_level_sets = 0; + + // Track unique faces by sorted vertex set → face_id. + std::map, int> face_map; + + const int n_cells = ac.n_entities(ac.tdim); + std::vector> face_to_cells; + std::vector> cell_to_faces(static_cast(n_cells)); + for (int c = 0; c < n_cells; ++c) + { + const cell::type ctype = ac.entity_types[ac.tdim][static_cast(c)]; + auto cell_verts = ac.entity_to_vertex[ac.tdim][static_cast(c)]; + const int nf = cell::num_faces(ctype); + + for (int fi = 0; fi < nf; ++fi) + { + auto local_fv = cell::face_vertices(ctype, fi); + const int fsize = static_cast(local_fv.size()); + + // Build global-vertex face and sorted key + std::vector global_fv(static_cast(fsize)); + std::vector sorted_fv(static_cast(fsize)); + for (int j = 0; j < fsize; ++j) + { + global_fv[static_cast(j)] = + cell_verts[static_cast(local_fv[static_cast(j)])]; + sorted_fv[static_cast(j)] = global_fv[static_cast(j)]; + } + std::sort(sorted_fv.begin(), sorted_fv.end()); + + auto face_it = face_map.find(sorted_fv); + int face_id = -1; + if (face_it == face_map.end()) + { + face_id = ac.n_entities(2); + face_map[sorted_fv] = face_id; + ac.entity_types[2].push_back(cell::face_type(ctype, fi)); + for (int j = 0; j < fsize; ++j) + ac.entity_to_vertex[2].indices.push_back(global_fv[static_cast(j)]); + ac.entity_to_vertex[2].offsets.push_back( + static_cast(ac.entity_to_vertex[2].indices.size())); + face_to_cells.emplace_back(); + + const int host_cell_id = + (ac.tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_host_cell_id[ac.tdim].size())) + ? ac.entity_host_cell_id[ac.tdim][static_cast(c)] + : (c < static_cast(ac.cell_source_cell_id.size())) + ? ac.cell_source_cell_id[static_cast(c)] + : c; + const cell::type host_cell_type = + (ac.tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_host_cell_type[ac.tdim].size())) + ? ac.entity_host_cell_type[ac.tdim][static_cast(c)] + : ctype; + const int source_level_set = + (ac.tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_source_level_set[ac.tdim].size())) + ? ac.entity_source_level_set[ac.tdim][static_cast(c)] + : -1; + const auto host_vertices = + host_vertices_for_entity(ac, ac.tdim, c); + append_entity_host_provenance( + ac, 2, host_cell_id, host_cell_type, -1, source_level_set, + host_vertices.empty() ? std::span(cell_verts) + : host_vertices); + } + else + face_id = face_it->second; + + face_to_cells[static_cast(face_id)].push_back( + static_cast(c)); + cell_to_faces[static_cast(c)].push_back( + static_cast(face_id)); + } + } + + auto fill_connectivity = [](EntityAdjacency& adjacency, + const std::vector>& rows) + { + adjacency.indices.clear(); + adjacency.offsets.clear(); + adjacency.offsets.push_back(std::int32_t(0)); + for (const auto& row : rows) + { + for (const auto value : row) + adjacency.indices.push_back(value); + adjacency.offsets.push_back( + static_cast(adjacency.indices.size())); + } + }; + fill_connectivity(ac.connectivity[2][ac.tdim], face_to_cells); + ac.has_connectivity[2][ac.tdim] = 1; + fill_connectivity(ac.connectivity[ac.tdim][2], cell_to_faces); + ac.has_connectivity[ac.tdim][2] = 1; +} + +// --------------------------------------------------------------------------- +// build_edges +// --------------------------------------------------------------------------- + +template +void build_edges(AdaptCell& ac) +{ + const int tdim = ac.tdim; + + // Clear existing 1D entity pool. + ac.entity_types[1].clear(); + ac.entity_to_vertex[1].offsets.clear(); + ac.entity_to_vertex[1].indices.clear(); + ac.entity_to_vertex[1].offsets.push_back(std::int32_t(0)); + clear_entity_host_provenance(ac, 1); + + // Track unique edges as (min_v, max_v) → edge_id. + std::map, int> edge_map; + + const int n_cells = ac.n_entities(tdim); + for (int c = 0; c < n_cells; ++c) + { + const cell::type ctype = ac.entity_types[tdim][static_cast(c)]; + auto cell_verts = ac.entity_to_vertex[tdim][static_cast(c)]; + auto cell_edges = cell::edges(ctype); + + for (const auto& ce : cell_edges) + { + const std::int32_t lv0 = cell_verts[static_cast(ce[0])]; + const std::int32_t lv1 = cell_verts[static_cast(ce[1])]; + const auto key = std::make_pair(std::min(lv0, lv1), std::max(lv0, lv1)); + + if (edge_map.find(key) == edge_map.end()) + { + edge_map[key] = ac.n_entities(1); + ac.entity_types[1].push_back(cell::type::interval); + ac.entity_to_vertex[1].indices.push_back(lv0); + ac.entity_to_vertex[1].indices.push_back(lv1); + ac.entity_to_vertex[1].offsets.push_back( + static_cast(ac.entity_to_vertex[1].indices.size())); + + const int host_cell_id = + (tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_host_cell_id[tdim].size())) + ? ac.entity_host_cell_id[tdim][static_cast(c)] + : (c < static_cast(ac.cell_source_cell_id.size())) + ? ac.cell_source_cell_id[static_cast(c)] + : c; + const cell::type host_cell_type = + (tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_host_cell_type[tdim].size())) + ? ac.entity_host_cell_type[tdim][static_cast(c)] + : ctype; + const int source_level_set = + (tdim < AdaptCell::max_dim + && c < static_cast(ac.entity_source_level_set[tdim].size())) + ? ac.entity_source_level_set[tdim][static_cast(c)] + : -1; + const auto host_vertices = + host_vertices_for_entity(ac, tdim, c); + append_entity_host_provenance( + ac, 1, host_cell_id, host_cell_type, -1, source_level_set, + host_vertices.empty() ? std::span(cell_verts) + : host_vertices); + } + } + } +} + +// --------------------------------------------------------------------------- +// make_adapt_cell +// --------------------------------------------------------------------------- + +template +AdaptCell make_adapt_cell(const MeshView& mesh, I cell_id) +{ + if (!mesh.has_cell_types()) + throw std::runtime_error( + "make_adapt_cell: MeshView must have cell types"); + + const cell::type ctype = mesh.cell_type(cell_id); + + const int tdim = cell::get_tdim(ctype); + const int gdim = mesh.gdim; + const int nv = cell::get_num_vertices(ctype); + + AdaptCell ac; + ac.gdim = gdim; + ac.tdim = tdim; + ac.parent_cell_type = ctype; + ac.parent_cell_id = static_cast(cell_id); + + // Reference vertex coordinates: canonical corners of the reference element. + // Stored as nv * tdim values. + ac.vertex_coords = reference_vertices(ctype); + + // Vertex provenance: all vertices coincide with parent-mesh vertices (dim 0). + ac.vertex_parent_dim.assign(static_cast(nv), std::int8_t(0)); + ac.vertex_parent_id.resize(static_cast(nv)); + for (int v = 0; v < nv; ++v) + ac.vertex_parent_id[static_cast(v)] = + static_cast(mesh.cell_node(cell_id, static_cast(v))); + + // No parametric coordinates for parent vertices (empty param per vertex). + ac.vertex_parent_param_offset.assign(static_cast(nv + 1), + std::int32_t(0)); + // vertex_parent_param stays empty. + ac.vertex_source_edge_id.assign(static_cast(nv), std::int32_t(-1)); + + // Level-set sign masks start unset (bit not set = value is positive). + ac.zero_mask_per_vertex.assign(static_cast(nv), + std::uint64_t(0)); + ac.negative_mask_per_vertex.assign(static_cast(nv), + std::uint64_t(0)); + + // Entity pool at topological dimension: one entity = the full background cell. + ac.entity_types[tdim].push_back(ctype); + ac.entity_to_vertex[tdim].offsets = {std::int32_t(0), std::int32_t(nv)}; + ac.entity_to_vertex[tdim].indices.resize(static_cast(nv)); + for (int v = 0; v < nv; ++v) + ac.entity_to_vertex[tdim].indices[static_cast(v)] = + std::int32_t(v); + + ac.cell_source_cell_id.assign(1, std::int32_t(0)); + ac.cell_refinement_generation.assign(1, std::int32_t(0)); + ac.cell_refinement_reason.assign(1, CellRefinementReason::none); + ac.cell_host_parent_cell_id.assign(1, ac.parent_cell_id); + clear_entity_host_provenance(ac, tdim); + append_entity_host_provenance( + ac, + tdim, + 0, + ctype, + -1, + -1, + std::span( + ac.entity_to_vertex[tdim].indices.data(), + ac.entity_to_vertex[tdim].indices.size())); + + // Create edges and faces (if tdim=3) in the entity pools + build_edges(ac); + + if (tdim == 3) + build_faces(ac); + + recompute_active_level_set_masks(ac, /*num_level_sets=*/0); + + return ac; +} + +// --------------------------------------------------------------------------- +// fill_vertex_signs +// --------------------------------------------------------------------------- + +template +void fill_vertex_signs(AdaptCell& ac, + std::span vertex_ls_values, + int ls_index, + T tol){ + const int nv = ac.n_vertices(); + assert(static_cast(vertex_ls_values.size()) >= nv + && "fill_vertex_signs: vertex_ls_values too short"); + assert(ls_index >= 0 && ls_index < 64 + && "fill_vertex_signs: ls_index out of [0,63]"); + + const std::uint64_t bit = std::uint64_t(1) << ls_index; + + for (int v = 0; v < nv; ++v) + { + const T val = vertex_ls_values[static_cast(v)]; + if (std::fabs(val) < tol) + ac.zero_mask_per_vertex[static_cast(v)] |= bit; + else if (val < T(0)) + ac.negative_mask_per_vertex[static_cast(v)] |= bit; + } +} + +// --------------------------------------------------------------------------- +// recompute_active_level_set_masks +// --------------------------------------------------------------------------- + +template +void recompute_active_level_set_masks(AdaptCell& ac, int num_level_sets) +{ + const int tdim = ac.tdim; + const int n_cells = ac.n_entities(tdim); + + ac.cell_active_level_set_mask.assign(static_cast(n_cells), + std::uint64_t(0)); + ac.active_level_set_mask = 0; + + if (num_level_sets <= 0 || n_cells <= 0) + return; + + const int nls = std::min(num_level_sets, 64); + + for (int c = 0; c < n_cells; ++c) + { + auto verts = ac.entity_to_vertex[tdim][static_cast(c)]; + std::uint64_t leaf_mask = 0; + + for (int ls = 0; ls < nls; ++ls) + { + const std::uint64_t bit = std::uint64_t(1) << ls; + bool has_neg = false; + bool has_pos = false; + bool has_zero = false; + + for (const auto v : verts) + { + const auto zv = ac.zero_mask_per_vertex[static_cast(v)]; + const auto nv = ac.negative_mask_per_vertex[static_cast(v)]; + if ((zv & bit) != 0) + has_zero = true; + else if ((nv & bit) != 0) + has_neg = true; + else + has_pos = true; + } + + if (has_zero || (has_neg && has_pos)) + leaf_mask |= bit; + } + + ac.cell_active_level_set_mask[static_cast(c)] = leaf_mask; + ac.active_level_set_mask |= leaf_mask; + } +} + +// --------------------------------------------------------------------------- +// rebuild_zero_entity_inventory +// --------------------------------------------------------------------------- + +template +void rebuild_zero_entity_inventory(AdaptCell& ac) +{ + ac.zero_entity_dim.clear(); + ac.zero_entity_id.clear(); + ac.zero_entity_zero_mask.clear(); + ac.zero_entity_is_owned.clear(); + ac.zero_entity_parent_dim.clear(); + ac.zero_entity_parent_id.clear(); + ac.zero_entity_host_cell_id.clear(); + ac.zero_entity_host_cell_type.clear(); + ac.zero_entity_host_face_id.clear(); + ac.zero_entity_source_level_set.clear(); + ac.zero_entity_host_cell_vertices.offsets.clear(); + ac.zero_entity_host_cell_vertices.indices.clear(); + ac.zero_entity_host_cell_vertices.offsets.push_back(std::int32_t(0)); + + const int n_vertices = ac.n_vertices(); + for (int v = 0; v < n_vertices; ++v) + { + const auto mask = ac.zero_mask_per_vertex[static_cast(v)]; + if (mask == 0) + continue; + + ac.zero_entity_dim.push_back(std::uint8_t(0)); + ac.zero_entity_id.push_back(static_cast(v)); + ac.zero_entity_zero_mask.push_back(mask); + ac.zero_entity_is_owned.push_back(std::uint8_t(1)); + ac.zero_entity_parent_dim.push_back( + ac.vertex_parent_dim.empty() ? std::int8_t(-1) + : ac.vertex_parent_dim[static_cast(v)]); + ac.zero_entity_parent_id.push_back( + ac.vertex_parent_id.empty() ? std::int32_t(-1) + : ac.vertex_parent_id[static_cast(v)]); + ac.zero_entity_host_cell_id.push_back(std::int32_t(-1)); + ac.zero_entity_host_cell_type.push_back(cell::type::point); + ac.zero_entity_host_face_id.push_back(std::int32_t(-1)); + ac.zero_entity_source_level_set.push_back(std::int32_t(-1)); + ac.zero_entity_host_cell_vertices.offsets.push_back( + static_cast(ac.zero_entity_host_cell_vertices.indices.size())); + } + + for (int dim = 1; dim < ac.tdim; ++dim) + { + const int n_entities = ac.n_entities(dim); + for (int e = 0; e < n_entities; ++e) + { + auto verts = ac.entity_to_vertex[dim][static_cast(e)]; + if (verts.empty()) + continue; + + std::uint64_t mask = ~std::uint64_t(0); + for (const auto v : verts) + { + mask &= ac.zero_mask_per_vertex[static_cast(v)]; + if (mask == 0) + break; + } + if (mask == 0) + continue; + + ac.zero_entity_dim.push_back(static_cast(dim)); + ac.zero_entity_id.push_back(static_cast(e)); + ac.zero_entity_zero_mask.push_back(mask); + ac.zero_entity_is_owned.push_back(std::uint8_t(1)); + const auto host = infer_zero_entity_parent_host(ac, verts); + ac.zero_entity_parent_dim.push_back(host.first); + ac.zero_entity_parent_id.push_back(host.second); + if (e < static_cast(ac.entity_host_cell_id[dim].size())) + { + ac.zero_entity_host_cell_id.push_back( + ac.entity_host_cell_id[dim][static_cast(e)]); + ac.zero_entity_host_cell_type.push_back( + ac.entity_host_cell_type[dim][static_cast(e)]); + ac.zero_entity_host_face_id.push_back( + ac.entity_host_face_id[dim][static_cast(e)]); + ac.zero_entity_source_level_set.push_back( + ac.entity_source_level_set[dim][static_cast(e)]); + const auto host_vertices = + ac.entity_host_cell_vertices[dim][static_cast(e)]; + for (const auto hv : host_vertices) + ac.zero_entity_host_cell_vertices.indices.push_back(hv); + } + else + { + ac.zero_entity_host_cell_id.push_back(std::int32_t(-1)); + ac.zero_entity_host_cell_type.push_back(cell::type::point); + ac.zero_entity_host_face_id.push_back(std::int32_t(-1)); + ac.zero_entity_source_level_set.push_back(std::int32_t(-1)); + } + ac.zero_entity_host_cell_vertices.offsets.push_back( + static_cast(ac.zero_entity_host_cell_vertices.indices.size())); + } + } + + ++ac.zero_entity_version; +} + +// --------------------------------------------------------------------------- +// Explicit template instantiations +// --------------------------------------------------------------------------- + +template void build_edges(AdaptCell&); +template void build_edges(AdaptCell&); + +template void build_faces(AdaptCell&); +template void build_faces(AdaptCell&); + +template AdaptCell make_adapt_cell(const MeshView&, int); +template AdaptCell make_adapt_cell(const MeshView&, int); +template AdaptCell make_adapt_cell(const MeshView&, long); +template AdaptCell make_adapt_cell(const MeshView&, long); + +template void fill_vertex_signs(AdaptCell&, std::span, int, double); +template void fill_vertex_signs(AdaptCell&, std::span, int, float); +template void recompute_active_level_set_masks(AdaptCell&, int); +template void recompute_active_level_set_masks(AdaptCell&, int); +template void rebuild_zero_entity_inventory(AdaptCell&); +template void rebuild_zero_entity_inventory(AdaptCell&); + +} // namespace cutcells diff --git a/cpp/src/adapt_cell.h b/cpp/src/adapt_cell.h new file mode 100644 index 0000000..d376fd4 --- /dev/null +++ b/cpp/src/adapt_cell.h @@ -0,0 +1,596 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +#include "cell_types.h" +#include "mesh_view.h" + +namespace cutcells +{ + +// --------------------------------------------------------------- +// Tag enumerations for certification +// --------------------------------------------------------------- + +/// Per-edge root classification for one level set. +enum class EdgeRootTag : std::uint8_t +{ + not_classified = 0, + no_root = 1, + one_root = 2, + multiple_roots = 3, + zero = 4 +}; + +/// Per-leaf-cell certification state for one level set. +enum class CellCertTag : std::uint8_t +{ + not_classified = 0, + positive = 1, + negative = 2, + cut = 3, + zero = 4, + ambiguous = 5, + ready_to_cut = 6 +}; + +/// Per-face certification state for one level set. +/// A face is a 2D entity; its sign/cut state is analogous to a cell. +enum class FaceCertTag : std::uint8_t +{ + not_classified = 0, + positive = 1, + negative = 2, + cut = 3, + zero = 4, + ambiguous = 5 +}; + +/// Why a leaf cell was created from an earlier leaf cell. +enum class CellRefinementReason : std::uint8_t +{ + none = 0, + green_edge = 1, + red_cell = 2, + targeted_green_edge = 3, + targeted_red_cell = 4, + cut_level_set = 5 +}; + +/// Compact Sparse Row (CSR) adjacency map between entities. +/// +/// Stores a variable-length list of adjacent entity indices for each source entity. +/// `offsets[i]` and `offsets[i+1]` delimit the slice of `indices` for entity `i`. +/// +/// Used for: +/// - `entity_to_vertex[d]`: maps entity i of dimension d to its vertex indices +/// - `connectivity[d0][d1]`: maps entities of dimension d0 to entities of dimension d1 +struct EntityAdjacency +{ + /// offsets into `indices`; size = n_entities + 1. offsets[0] == 0 always. + std::vector offsets; + + /// Flat list of adjacent entity ids. + std::vector indices; + + /// Number of source entities. + std::int32_t size() const noexcept + { + return offsets.empty() ? 0 : static_cast(offsets.size()) - 1; + } + + /// Return the adjacency list for source entity `i`. + std::span operator[](std::int32_t i) const + { + return {indices.data() + offsets[i], + static_cast(offsets[i + 1] - offsets[i])}; + } + + /// Return a mutable adjacency list for source entity `i`. + std::span operator[](std::int32_t i) + { + return {indices.data() + offsets[i], + static_cast(offsets[i + 1] - offsets[i])}; + } +}; + +/// AdaptCell is the main local container for one background cell. +/// It is a dynamic mesh in reference coordinates: vertices carry the rich +/// semantic data (coordinates, level-set signs, provenance), and entities of +/// dimension 1, 2, 3 are defined purely through their vertex references. +/// Only leaf entities are stored — no refinement tree. +template +struct AdaptCell +{ + // --------------------------------------------------------------- + // Metadata + // --------------------------------------------------------------- + + int gdim = 0; ///< geometric dimension of the embedding space + int tdim = 0; ///< topological dimension (2 for triangles/quads, 3 for tets/hexes) + + cell::type parent_cell_type = cell::type::point; ///< type of the original background cell + int parent_cell_id = -1; ///< index of the background cell in the mesh + + ///< bit i set → level set i is active on this cell (cuts through it) + std::uint64_t active_level_set_mask = 0; + + /// Per-leaf-cell active level-set mask. + /// Parallel to entity_types[tdim]/entity_to_vertex[tdim]: + /// bit i set ↔ level set i intersects that leaf cell. + std::vector cell_active_level_set_mask; + + /// Persistent leaf-cell provenance. These arrays are parallel to the + /// top-dimensional entity pool and are preserved through repeated + /// adaptcell refinements. + std::vector cell_source_cell_id; + std::vector cell_refinement_generation; + std::vector cell_refinement_reason; + std::vector cell_host_parent_cell_id; + + // --------------------------------------------------------------- + // Vertices (dimension 0) + // + // Vertices are the primary data carriers. They are NOT stored in + // the entity pools below. All arrays are parallel, indexed by + // vertex id v = 0, 1, ..., n_vertices()-1. + // --------------------------------------------------------------- + + /// Reference coordinates of each vertex, flattened: [v0_xi0, v0_xi1, ..., v1_xi0, ...]. + /// These are coordinates in the reference cell of the parent background cell. + std::vector vertex_coords; + + /// Tri-state sign storage per vertex and per level set. + /// For level set i (0-indexed): + /// bit i set in zero_mask[v] → φ_i(v) = 0 + /// bit i set in negative_mask[v] → φ_i(v) < 0 + /// neither → φ_i(v) > 0 + std::vector zero_mask_per_vertex; + std::vector negative_mask_per_vertex; + + /// Provenance: where in the ORIGINAL parent background cell did this vertex come from? + /// This is the key to global stitching without geometric deduplication. + /// + /// parent_entity_dim: 0 = parent vertex, 1 = parent edge, 2 = parent face, 3 = parent cell interior + /// parent_entity_id: GLOBAL mesh entity id (e.g. global edge 47, global vertex 12). + /// Using global ids makes stitching across background cells trivial: + /// two AdaptCells sharing an edge both reference the same global edge id, + /// so matching vertices requires only (parent_entity_id, parent_param) comparison. + /// parent_local_param: parametric coordinate(s) within the parent entity. + /// For a vertex born on a parent edge: one parameter t ∈ [0,1]. + /// For a vertex born on a parent face: two parameters (u,v). + /// For a vertex coinciding with a parent vertex: empty. + /// For a vertex in the cell interior: tdim parameters. + /// Stored flat; use parent_entity_dim to know how many params per vertex. + std::vector vertex_parent_dim; + std::vector vertex_parent_id; + std::vector vertex_parent_param; ///< flat, variable-length per vertex + std::vector vertex_parent_param_offset; ///< size = n_vertices + 1 + std::vector vertex_source_edge_id; ///< leaf-edge provenance; -1 if not created from a leaf edge + + // --------------------------------------------------------------- + // Entity pools (dimensions 1 .. tdim) + // + // Each entity of dimension d has: + // - a cell type (e.g. interval for d=1, triangle/quad for d=2, tet/hex for d=3) + // - an ordered list of vertex indices (via entity_to_vertex[d]) + // + // Entities are numbered 0, 1, ..., n_entities(d)-1 within each dimension. + // Only leaf (final, active) entities are stored. + // --------------------------------------------------------------- + + static constexpr int max_dim = 4; + + /// Cell type of each entity. entity_types[d][i] is the type of entity i at dimension d. + /// For d=1 this is always cell::type::interval, but stored for uniformity. + std::array, max_dim> entity_types; + + /// Canonical topology: entity → vertex. + /// entity_to_vertex[d] stores the vertex indices for each entity of dimension d. + /// This is CSR-style: offsets + flat indices. + std::array entity_to_vertex; + + /// Structured embedding-host provenance for entities. For zero entities + /// created by a cut, this is the uncut AdaptCell leaf that embeds the + /// interface. It is deliberately not the + /// accepted cut output cell that happens to contain the entity. + /// For zero edges on a zero face boundary, host_face_id is filled by the + /// face context when available. + std::array, max_dim> entity_host_cell_id; + std::array, max_dim> entity_host_cell_type; + std::array, max_dim> entity_host_face_id; + std::array, max_dim> entity_source_level_set; + std::array entity_host_cell_vertices; + + // --------------------------------------------------------------- + // Connectivity cache (d0 → d1) + // + // Generic adjacency between entities of different dimensions. + // Built on demand, not stored by default. + // The canonical source is always entity_to_vertex; everything + // else is derived from it. + // --------------------------------------------------------------- + + std::array, max_dim> connectivity; + std::array, max_dim> has_connectivity = {}; + + // --------------------------------------------------------------- + // Zero-entity inventory (semantic layer for interfaces and intersections) + // + // A zero entity is an entity where one or more level sets vanish. + // Each entry references an entity in the topology pools above. + // This is the layer used for selections like "phi = 0" and + // "phi1 = 0 and phi2 = 0". + // + // All arrays are parallel, indexed by zero-entity id z = 0, 1, ... + // --------------------------------------------------------------- + + std::vector zero_entity_dim; ///< dimension of the entity (1, 2, ...) + std::vector zero_entity_id; ///< index into entity_types[dim] / entity_to_vertex[dim] + std::vector zero_entity_zero_mask; ///< which level sets vanish: bit i set → φ_i = 0 on this entity + std::vector zero_entity_is_owned; ///< ownership flag for global assembly (avoids double-counting) + + /// Provenance of the zero entity back to the parent background cell. + /// A zero entity born on parent face 2 gets parent_dim=2, parent_id=2. + /// This enables global stitching of interface meshes. + std::vector zero_entity_parent_dim; ///< -1 if not on a shared parent entity + std::vector zero_entity_parent_id; ///< -1 if not on a shared parent entity + + /// Embedding-host provenance for the zero entity. These arrays mirror + /// entity_host_* for the zero-entity inventory and survive inventory + /// rebuilds as long as the underlying entity provenance is available. + std::vector zero_entity_host_cell_id; + std::vector zero_entity_host_cell_type; + std::vector zero_entity_host_face_id; + std::vector zero_entity_source_level_set; + EntityAdjacency zero_entity_host_cell_vertices; + + std::uint32_t zero_entity_version = 0; ///< bumped when zero-entity inventory changes + + // --------------------------------------------------------------- + // Per-level-set edge tags + // + // Flat storage: edge_root_tag[ls_id * n_edges(1) + edge_id] + // --------------------------------------------------------------- + + std::vector edge_root_tag; + int edge_root_tag_num_level_sets = 0; + + EdgeRootTag get_edge_root_tag(int level_set_id, int edge_id) const + { + return edge_root_tag[static_cast( + level_set_id * n_entities(1) + edge_id)]; + } + + void set_edge_root_tag(int level_set_id, int edge_id, EdgeRootTag tag) + { + edge_root_tag[static_cast( + level_set_id * n_entities(1) + edge_id)] = tag; + } + + void resize_edge_root_tags(int num_level_sets) + { + const int old_num_level_sets = edge_root_tag_num_level_sets; + const int old_n_edges = (old_num_level_sets > 0) + ? static_cast(edge_root_tag.size()) + / old_num_level_sets + : 0; + const int new_n_edges = n_entities(1); + + std::vector new_tags( + static_cast(num_level_sets * new_n_edges), + EdgeRootTag::not_classified); + + const int copy_ls = std::min(old_num_level_sets, num_level_sets); + const int copy_edges = std::min(old_n_edges, new_n_edges); + for (int ls = 0; ls < copy_ls; ++ls) + { + for (int e = 0; e < copy_edges; ++e) + { + new_tags[static_cast(ls * new_n_edges + e)] = + edge_root_tag[static_cast(ls * old_n_edges + e)]; + } + } + + edge_root_tag_num_level_sets = num_level_sets; + edge_root_tag = std::move(new_tags); + } + + // --------------------------------------------------------------- + // Per-level-set cell certification tags + // + // Flat storage: cell_cert_tag[ls_id * n_entities(tdim) + cell_id] + // --------------------------------------------------------------- + + std::vector cell_cert_tag; + int cell_cert_tag_num_level_sets = 0; + + CellCertTag get_cell_cert_tag(int level_set_id, int cell_id) const + { + return cell_cert_tag[static_cast( + level_set_id * n_entities(tdim) + cell_id)]; + } + + void set_cell_cert_tag(int level_set_id, int cell_id, CellCertTag tag) + { + cell_cert_tag[static_cast( + level_set_id * n_entities(tdim) + cell_id)] = tag; + } + + void resize_cell_cert_tags(int num_level_sets) + { + const int old_num_level_sets = cell_cert_tag_num_level_sets; + const int old_n_cells = (old_num_level_sets > 0) + ? static_cast(cell_cert_tag.size()) + / old_num_level_sets + : 0; + const int new_n_cells = n_entities(tdim); + + std::vector new_tags( + static_cast(num_level_sets * new_n_cells), + CellCertTag::not_classified); + + const int copy_ls = std::min(old_num_level_sets, num_level_sets); + const int copy_cells = std::min(old_n_cells, new_n_cells); + for (int ls = 0; ls < copy_ls; ++ls) + { + for (int c = 0; c < copy_cells; ++c) + { + new_tags[static_cast(ls * new_n_cells + c)] = + cell_cert_tag[static_cast(ls * old_n_cells + c)]; + } + } + + cell_cert_tag_num_level_sets = num_level_sets; + cell_cert_tag = std::move(new_tags); + } + + // --------------------------------------------------------------- + // Green-split metadata per edge per level set + // + // Flat storage: edge_green_split_param[ls_id * n_edges + edge_id] + // edge_green_split_has_value: nonzero if the param is valid + // --------------------------------------------------------------- + + std::vector edge_green_split_param; + std::vector edge_green_split_has_value; + + void resize_green_split_data(int num_level_sets) + { + const int new_n_edges = n_entities(1); + const int old_num_level_sets = (!edge_green_split_param.empty() && new_n_edges > 0) + ? static_cast(edge_green_split_param.size()) + / new_n_edges + : 0; + const int old_n_edges = new_n_edges; + const auto n = static_cast(num_level_sets * new_n_edges); + + std::vector new_params(n, T(0)); + std::vector new_has_value(n, std::uint8_t(0)); + + const int copy_ls = std::min(old_num_level_sets, num_level_sets); + const int copy_edges = std::min(old_n_edges, new_n_edges); + for (int ls = 0; ls < copy_ls; ++ls) + { + for (int e = 0; e < copy_edges; ++e) + { + new_params[static_cast(ls * new_n_edges + e)] = + edge_green_split_param[static_cast(ls * old_n_edges + e)]; + new_has_value[static_cast(ls * new_n_edges + e)] = + edge_green_split_has_value[static_cast(ls * old_n_edges + e)]; + } + } + + edge_green_split_param = std::move(new_params); + edge_green_split_has_value = std::move(new_has_value); + } + + // --------------------------------------------------------------- + // One-root metadata per edge per level set + // + // Stores the localized root parameter on leaf edges tagged one_root + // together with the AdaptCell vertex id created for that root. + // --------------------------------------------------------------- + + std::vector edge_one_root_param; + std::vector edge_one_root_vertex_id; + std::vector edge_one_root_has_value; + + void resize_one_root_data(int num_level_sets) + { + const int new_n_edges = n_entities(1); + const int old_num_level_sets = (!edge_one_root_param.empty() && new_n_edges > 0) + ? static_cast(edge_one_root_param.size()) + / new_n_edges + : 0; + const int old_n_edges = new_n_edges; + const auto n = static_cast(num_level_sets * new_n_edges); + + std::vector new_params(n, T(0)); + std::vector new_vertex_ids(n, std::int32_t(-1)); + std::vector new_has_value(n, std::uint8_t(0)); + + const int copy_ls = std::min(old_num_level_sets, num_level_sets); + const int copy_edges = std::min(old_n_edges, new_n_edges); + for (int ls = 0; ls < copy_ls; ++ls) + { + for (int e = 0; e < copy_edges; ++e) + { + new_params[static_cast(ls * new_n_edges + e)] = + edge_one_root_param[static_cast(ls * old_n_edges + e)]; + new_vertex_ids[static_cast(ls * new_n_edges + e)] = + edge_one_root_vertex_id[static_cast(ls * old_n_edges + e)]; + new_has_value[static_cast(ls * new_n_edges + e)] = + edge_one_root_has_value[static_cast(ls * old_n_edges + e)]; + } + } + + edge_one_root_param = std::move(new_params); + edge_one_root_vertex_id = std::move(new_vertex_ids); + edge_one_root_has_value = std::move(new_has_value); + } + + // --------------------------------------------------------------- + // Convenience accessors + // --------------------------------------------------------------- + + /// Number of vertices in this cell + int n_vertices() const + { + return tdim > 0 ? static_cast(vertex_coords.size()) / tdim : 0; + } + + /// Number of entities of dimension d + int n_entities(int d) const + { + return (d >= 1 && d < max_dim) + ? static_cast(entity_types[d].size()) + : 0; + } + + /// Check if connectivity map between dimensions d0 and d1 exists + bool has_connectivity_map(int d0, int d1) const + { + return (d0 >= 1 && d0 < max_dim && d1 >= 1 && d1 < max_dim) + ? (has_connectivity[d0][d1] != 0) + : false; + } + + /// Number of zero entities + int n_zero_entities() const + { + return static_cast(zero_entity_id.size()); + } + + // --------------------------------------------------------------- + // Per-level-set face certification tags (3D only) + // + // Flat storage: face_cert_tag[ls_id * n_entities(2) + face_id] + // --------------------------------------------------------------- + + std::vector face_cert_tag; + int face_cert_tag_num_level_sets = 0; + + FaceCertTag get_face_cert_tag(int level_set_id, int face_id) const + { + return face_cert_tag[static_cast( + level_set_id * n_entities(2) + face_id)]; + } + + void set_face_cert_tag(int level_set_id, int face_id, FaceCertTag tag) + { + face_cert_tag[static_cast( + level_set_id * n_entities(2) + face_id)] = tag; + } + + void resize_face_cert_tags(int num_level_sets) + { + const int old_num_level_sets = face_cert_tag_num_level_sets; + const int old_n_faces = (old_num_level_sets > 0) + ? static_cast(face_cert_tag.size()) + / old_num_level_sets + : 0; + const int new_n_faces = n_entities(2); + + std::vector new_tags( + static_cast(num_level_sets * new_n_faces), + FaceCertTag::not_classified); + + const int copy_ls = std::min(old_num_level_sets, num_level_sets); + const int copy_faces = std::min(old_n_faces, new_n_faces); + for (int ls = 0; ls < copy_ls; ++ls) + { + for (int f = 0; f < copy_faces; ++f) + { + new_tags[static_cast(ls * new_n_faces + f)] = + face_cert_tag[static_cast(ls * old_n_faces + f)]; + } + } + + face_cert_tag_num_level_sets = num_level_sets; + face_cert_tag = std::move(new_tags); + } +}; + +/// Create an AdaptCell initialised from a single background cell in a MeshView. +/// +/// The returned cell owns its own storage. Every vertex is tagged with +/// provenance dimension 0 (= parent vertex) and the global mesh node id. +/// Reference coordinates follow the canonical ordering defined in mapping.h. +/// Level-set sign masks are zeroed (all-positive) and must be filled +/// separately with fill_vertex_signs(). +/// +/// @param mesh Background mesh. MeshView::cell_types must be present +/// (cutcells integer codes). +/// @param cell_id Index of the cell to extract. +/// @return Initialised AdaptCell in reference space. +template +AdaptCell make_adapt_cell(const MeshView& mesh, I cell_id); + +/// Fill the vertex-level-set sign masks of an AdaptCell for one level set. +/// +/// @param ac AdaptCell whose masks are updated in place. +/// @param vertex_ls_values Level-set values at the vertices of the cell, +/// in the same vertex ordering as ac.vertex_coords. +/// Must have at least ac.n_vertices() entries. +/// @param ls_index Which level set (bit position, 0–63). +/// @param tol Absolute tolerance for the zero test. +template +void fill_vertex_signs(AdaptCell& ac, + std::span vertex_ls_values, + int ls_index, + T tol = T(1e-14)); + +/// Build (or rebuild) entity_to_vertex[1] from all top-dimensional leaf cells. +/// +/// Clears any existing 1D entity pool, then re-derives edges by iterating +/// every cell in entity_to_vertex[tdim] and adding vertex-pairs from the +/// cell type's edge table (cell_topology.h ordering). Duplicate edges are +/// suppressed; each unique (min_v, max_v) pair appears once. +/// +/// @param ac AdaptCell to update in place. +template +void build_edges(AdaptCell& ac); + +/// Build (or rebuild) entity_to_vertex[2] from all top-dimensional leaf cells. +/// +/// Only meaningful for 3D cells (tdim == 3). Clears any existing 2D entity +/// pool, then re-derives faces by iterating every cell in entity_to_vertex[3] +/// and adding vertex-tuples from the cell type's face table +/// (cell_topology.h ordering). Duplicate faces are suppressed; each unique +/// sorted vertex set appears once. +/// +/// @param ac AdaptCell to update in place. +template +void build_faces(AdaptCell& ac); + +/// Recompute per-leaf and per-cell active level-set masks from vertex masks. +/// +/// For each top-dimensional leaf cell, bit i is set if the cell has a +/// sign change for level set i or contains at least one zero vertex for i. +/// +/// @param ac AdaptCell to update in place. +/// @param num_level_sets Number of level sets to scan (bits 0..num_level_sets-1). +template +void recompute_active_level_set_masks(AdaptCell& ac, int num_level_sets); + +/// Rebuild the zero-entity inventory from current topology and vertex masks. +/// +/// Zero entities are registered for: +/// - dim 0: vertices with nonzero zero-mask +/// - dim 1..tdim-1: entities whose all vertices share at least one zero bit +/// (mask = intersection of vertex zero masks). +/// +/// @param ac AdaptCell to update in place. +template +void rebuild_zero_entity_inventory(AdaptCell& ac); + +} // namespace cutcells diff --git a/cpp/src/bernstein.cpp b/cpp/src/bernstein.cpp new file mode 100644 index 0000000..ce4e5bd --- /dev/null +++ b/cpp/src/bernstein.cpp @@ -0,0 +1,1052 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "bernstein.h" + +#include +#include +#include +#include +#include +#include + +namespace cutcells::bernstein +{ +namespace +{ + +// --------------------------------------------------------------------------- +// Arithmetic helpers +// --------------------------------------------------------------------------- + +/// Compute base^exp for non-negative integer exponent. +/// Handles 0^0 = 1 correctly. +template +T integer_pow(T base, int exp) +{ + T result = T(1); + for (int i = 0; i < exp; ++i) + result *= base; + return result; +} + +/// Binomial coefficient C(n, k) computed without overflow for moderate n. +template +T binomial(int n, int k) +{ + if (k < 0 || k > n) + return T(0); + if (k == 0 || k == n) + return T(1); + if (k > n - k) + k = n - k; + T result = T(1); + for (int i = 0; i < k; ++i) + { + result *= T(n - i); + result /= T(i + 1); + } + return result; +} + +/// Multinomial coefficient n! / (alpha[0]! * ... * alpha[d]!) +/// computed as a product of binomial coefficients. +/// alpha must satisfy sum(alpha) == n. +template +T multinomial(int n, const int* alpha, int num_components) +{ + T result = T(1); + int remaining = n; + for (int c = 1; c < num_components; ++c) + { + result *= binomial(remaining, alpha[c]); + remaining -= alpha[c]; + } + return result; +} + +// --------------------------------------------------------------------------- +// Simplex multi-index → linear index +// +// Enumeration order (matching evaluate loops): +// tdim=1: i = 0..n → alpha = (n-i, i) +// tdim=2: j = 0..n, i = 0..n-j → alpha = (n-i-j, i, j) +// tdim=3: k = 0..n, j = 0..n-k, i = 0..n-k-j → alpha = (n-i-j-k, i, j, k) +// --------------------------------------------------------------------------- + +inline int simplex_index_1d(int i, int /*n*/) +{ + return i; +} + +inline int simplex_index_2d(int i, int j, int n) +{ + return j * (n + 1) - j * (j - 1) / 2 + i; +} + +inline int simplex_index_3d(int i, int j, int k, int n) +{ + // Offset for layers 0..k-1 + int offset = 0; + for (int kk = 0; kk < k; ++kk) + { + int m = n - kk; + offset += (m + 1) * (m + 2) / 2; + } + // Within layer k, it is a triangle of degree n-k + return offset + simplex_index_2d(i, j, n - k); +} + +// --------------------------------------------------------------------------- +// 1D Bernstein helpers (used by tensor-product evaluation) +// --------------------------------------------------------------------------- + +/// Evaluate all (degree+1) 1D Bernstein basis functions at t. +template +void bernstein_1d_all(int degree, T t, T* out) +{ + int n = degree; + for (int i = 0; i <= n; ++i) + out[i] = binomial(n, i) * integer_pow(t, i) * integer_pow(T(1) - t, n - i); +} + +/// Evaluate all degree 1D Bernstein basis functions of degree (n-1) at t, +/// and also return the derivative of each degree-n Bernstein basis function. +/// dB^n_i(t) = n * [B^{n-1}_{i-1}(t) - B^{n-1}_i(t)] +template +void bernstein_1d_deriv(int degree, T t, T* dbasis) +{ + int n = degree; + if (n == 0) + return; + + // Evaluate degree-(n-1) basis + std::vector b(static_cast(n)); // n = degree, n-1+1 = n entries + bernstein_1d_all(n - 1, t, b.data()); + + for (int i = 0; i <= n; ++i) + { + T bim1 = (i > 0) ? b[static_cast(i - 1)] : T(0); + T bi = (i < n) ? b[static_cast(i)] : T(0); + dbasis[i] = T(n) * (bim1 - bi); + } +} + +// --------------------------------------------------------------------------- +// Dense linear solver (Gaussian elimination with partial pivoting) +// Row-major storage: A[row * n + col] +// --------------------------------------------------------------------------- + +template +void solve_dense(int n, std::vector& A, std::vector& b) +{ + // Forward elimination + for (int col = 0; col < n; ++col) + { + // Partial pivoting + int max_row = col; + T max_val = std::abs(A[static_cast(col) * static_cast(n) + + static_cast(col)]); + for (int row = col + 1; row < n; ++row) + { + T val = std::abs(A[static_cast(row) * static_cast(n) + + static_cast(col)]); + if (val > max_val) + { + max_val = val; + max_row = row; + } + } + if (max_val < T(1e-14)) + throw std::runtime_error("bernstein: singular matrix in Lagrange-to-Bernstein conversion"); + + // Swap rows + if (max_row != col) + { + for (int j = col; j < n; ++j) + std::swap(A[static_cast(col) * static_cast(n) + + static_cast(j)], + A[static_cast(max_row) * static_cast(n) + + static_cast(j)]); + std::swap(b[static_cast(col)], + b[static_cast(max_row)]); + } + + // Eliminate below pivot + for (int row = col + 1; row < n; ++row) + { + T factor = A[static_cast(row) * static_cast(n) + + static_cast(col)] + / A[static_cast(col) * static_cast(n) + + static_cast(col)]; + for (int j = col + 1; j < n; ++j) + { + A[static_cast(row) * static_cast(n) + + static_cast(j)] + -= factor * A[static_cast(col) * static_cast(n) + + static_cast(j)]; + } + b[static_cast(row)] -= factor * b[static_cast(col)]; + } + } + + // Back substitution + for (int row = n - 1; row >= 0; --row) + { + for (int j = row + 1; j < n; ++j) + { + b[static_cast(row)] + -= A[static_cast(row) * static_cast(n) + + static_cast(j)] + * b[static_cast(j)]; + } + b[static_cast(row)] + /= A[static_cast(row) * static_cast(n) + + static_cast(row)]; + } +} + +template +struct TransformKey +{ + cell::type ctype = cell::type::point; + int degree = 0; + std::vector ref_points; + + bool operator==(const TransformKey&) const = default; +}; + +template +struct TransformKeyHash +{ + std::size_t operator()(const TransformKey& key) const noexcept + { + std::size_t seed = std::hash{}(static_cast(key.ctype)); + seed ^= std::hash{}(key.degree) + 0x9e3779b97f4a7c15ULL + + (seed << 6) + (seed >> 2); + seed ^= std::hash{}(key.ref_points.size()) + 0x9e3779b97f4a7c15ULL + + (seed << 6) + (seed >> 2); + for (const T value : key.ref_points) + { + seed ^= std::hash{}(value) + 0x9e3779b97f4a7c15ULL + + (seed << 6) + (seed >> 2); + } + return seed; + } +}; + +template +std::vector build_lagrange_to_bernstein_transform(cell::type ctype, int degree, + std::span ref_points) +{ + const int N = num_polynomials(ctype, degree); + const int tdim = cell::get_tdim(ctype); + + std::vector V(static_cast(N) * static_cast(N)); + std::vector basis_row(static_cast(N)); + + for (int i = 0; i < N; ++i) + { + const T* pt = ref_points.data() + + static_cast(i) * static_cast(tdim); + evaluate_basis( + ctype, degree, + std::span(pt, static_cast(tdim)), + std::span(basis_row)); + + for (int j = 0; j < N; ++j) + { + V[static_cast(i) * static_cast(N) + + static_cast(j)] = basis_row[static_cast(j)]; + } + } + + // Build the inverse transform once so repeated conversions only need a + // dense matrix-vector multiply. + std::vector transform(static_cast(N) * static_cast(N), T(0)); + std::vector rhs(static_cast(N), T(0)); + for (int col = 0; col < N; ++col) + { + std::vector A = V; + std::fill(rhs.begin(), rhs.end(), T(0)); + rhs[static_cast(col)] = T(1); + solve_dense(N, A, rhs); + for (int row = 0; row < N; ++row) + { + transform[static_cast(row) * static_cast(N) + + static_cast(col)] = rhs[static_cast(row)]; + } + } + + return transform; +} + +template +const std::vector& cached_lagrange_to_bernstein_transform( + cell::type ctype, int degree, std::span ref_points) +{ + thread_local std::unordered_map, std::vector, TransformKeyHash> + cache; + + TransformKey key; + key.ctype = ctype; + key.degree = degree; + key.ref_points.assign(ref_points.begin(), ref_points.end()); + + auto it = cache.find(key); + if (it != cache.end()) + return it->second; + + auto transform = build_lagrange_to_bernstein_transform(ctype, degree, ref_points); + auto [inserted_it, inserted] = cache.emplace(std::move(key), std::move(transform)); + (void)inserted; + return inserted_it->second; +} + +// --------------------------------------------------------------------------- +// Simplex Bernstein evaluation +// --------------------------------------------------------------------------- + +template +T evaluate_simplex(int tdim, int degree, const T* coeffs, const T* xi) +{ + const int n = degree; + + // Barycentric coordinates: lambda[0] = 1 - sum(xi), lambda[k+1] = xi[k] + T lambda[4]; + T sum = T(0); + for (int d = 0; d < tdim; ++d) + sum += xi[d]; + lambda[0] = T(1) - sum; + for (int d = 0; d < tdim; ++d) + lambda[d + 1] = xi[d]; + + T result = T(0); + int idx = 0; + + if (tdim == 1) + { + for (int i = 0; i <= n; ++i) + { + int alpha[2] = {n - i, i}; + T b = multinomial(n, alpha, 2) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]); + result += coeffs[idx++] * b; + } + } + else if (tdim == 2) + { + for (int j = 0; j <= n; ++j) + { + for (int i = 0; i <= n - j; ++i) + { + int alpha[3] = {n - i - j, i, j}; + T b = multinomial(n, alpha, 3) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]); + result += coeffs[idx++] * b; + } + } + } + else if (tdim == 3) + { + for (int k = 0; k <= n; ++k) + { + for (int j = 0; j <= n - k; ++j) + { + for (int i = 0; i <= n - k - j; ++i) + { + int alpha[4] = {n - i - j - k, i, j, k}; + T b = multinomial(n, alpha, 4) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]) + * integer_pow(lambda[3], alpha[3]); + result += coeffs[idx++] * b; + } + } + } + } + + return result; +} + +// --------------------------------------------------------------------------- +// Tensor-product Bernstein evaluation +// --------------------------------------------------------------------------- + +template +T evaluate_tensor(int tdim, int degree, const T* coeffs, const T* xi) +{ + const int n = degree; + const int n1 = n + 1; + + if (tdim == 1) + { + // Same as simplex 1D + T result = T(0); + for (int i = 0; i <= n; ++i) + result += coeffs[i] * binomial(n, i) + * integer_pow(xi[0], i) + * integer_pow(T(1) - xi[0], n - i); + return result; + } + else if (tdim == 2) + { + // Precompute 1D basis in each direction + std::vector Bx(static_cast(n1)), By(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + + T result = T(0); + for (int i = 0; i <= n; ++i) + for (int j = 0; j <= n; ++j) + result += coeffs[i * n1 + j] * Bx[static_cast(i)] + * By[static_cast(j)]; + return result; + } + else if (tdim == 3) + { + std::vector Bx(static_cast(n1)), + By(static_cast(n1)), + Bz(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + bernstein_1d_all(n, xi[2], Bz.data()); + + T result = T(0); + for (int i = 0; i <= n; ++i) + for (int j = 0; j <= n; ++j) + for (int k = 0; k <= n; ++k) + result += coeffs[(i * n1 + j) * n1 + k] + * Bx[static_cast(i)] + * By[static_cast(j)] + * Bz[static_cast(k)]; + return result; + } + + return T(0); +} + +// --------------------------------------------------------------------------- +// Simplex Bernstein gradient +// +// Uses the derivative formula: +// df/d(xi_k) = n * sum_{|beta|=n-1} (c[beta+e_{k+1}] - c[beta+e_0]) * B^{n-1}_beta(xi) +// +// where e_0 corresponds to lambda_0 and e_{k+1} to lambda_{k+1} = xi_k. +// --------------------------------------------------------------------------- + +template +void gradient_simplex(int tdim, int degree, const T* coeffs, const T* xi, T* grad) +{ + const int n = degree; + + for (int d = 0; d < tdim; ++d) + grad[d] = T(0); + + if (n == 0) + return; + + // Barycentric coordinates + T lambda[4]; + T sum = T(0); + for (int d = 0; d < tdim; ++d) + sum += xi[d]; + lambda[0] = T(1) - sum; + for (int d = 0; d < tdim; ++d) + lambda[d + 1] = xi[d]; + + const int nm1 = n - 1; + + if (tdim == 1) + { + // df/dxi = n * sum_{i=0}^{n-1} (c[i+1] - c[i]) * B^{n-1}_i(xi) + T g = T(0); + for (int i = 0; i <= nm1; ++i) + { + int alpha[2] = {nm1 - i, i}; + T b = multinomial(nm1, alpha, 2) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]); + T dc = coeffs[i + 1] - coeffs[i]; + g += dc * b; + } + grad[0] = T(n) * g; + } + else if (tdim == 2) + { + // For direction k (0-indexed reference direction): + // df/d(xi_k) = n * sum_{beta with |beta|=n-1} + // (c[beta + e_{k+1}] - c[beta + e_0]) * B^{n-1}_beta(xi) + // + // beta = (nm1-i-j, i, j), enumerated by (j, i) + // beta + e_0 has index in degree-n array: simplex_index_2d(i, j, n) + // beta + e_1 has index: simplex_index_2d(i+1, j, n) + // beta + e_2 has index: simplex_index_2d(i, j+1, n) + + T g0 = T(0), g1 = T(0); + for (int j = 0; j <= nm1; ++j) + { + for (int i = 0; i <= nm1 - j; ++i) + { + int alpha[3] = {nm1 - i - j, i, j}; + T b = multinomial(nm1, alpha, 3) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]); + + int idx_e0 = simplex_index_2d(i, j, n); + int idx_e1 = simplex_index_2d(i + 1, j, n); + int idx_e2 = simplex_index_2d(i, j + 1, n); + + g0 += (coeffs[idx_e1] - coeffs[idx_e0]) * b; + g1 += (coeffs[idx_e2] - coeffs[idx_e0]) * b; + } + } + grad[0] = T(n) * g0; + grad[1] = T(n) * g1; + } + else if (tdim == 3) + { + T g0 = T(0), g1 = T(0), g2 = T(0); + for (int k = 0; k <= nm1; ++k) + { + for (int j = 0; j <= nm1 - k; ++j) + { + for (int i = 0; i <= nm1 - k - j; ++i) + { + int alpha[4] = {nm1 - i - j - k, i, j, k}; + T b = multinomial(nm1, alpha, 4) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]) + * integer_pow(lambda[3], alpha[3]); + + int idx_e0 = simplex_index_3d(i, j, k, n); + int idx_e1 = simplex_index_3d(i + 1, j, k, n); + int idx_e2 = simplex_index_3d(i, j + 1, k, n); + int idx_e3 = simplex_index_3d(i, j, k + 1, n); + + g0 += (coeffs[idx_e1] - coeffs[idx_e0]) * b; + g1 += (coeffs[idx_e2] - coeffs[idx_e0]) * b; + g2 += (coeffs[idx_e3] - coeffs[idx_e0]) * b; + } + } + } + grad[0] = T(n) * g0; + grad[1] = T(n) * g1; + grad[2] = T(n) * g2; + } +} + +// --------------------------------------------------------------------------- +// Tensor-product Bernstein gradient +// --------------------------------------------------------------------------- + +template +void gradient_tensor(int tdim, int degree, const T* coeffs, const T* xi, T* grad) +{ + const int n = degree; + const int n1 = n + 1; + + for (int d = 0; d < tdim; ++d) + grad[d] = T(0); + + if (tdim == 1) + { + // Same as simplex 1D gradient + if (n == 0) + return; + std::vector bm1(static_cast(n)); + bernstein_1d_all(n - 1, xi[0], bm1.data()); + + T g = T(0); + for (int i = 0; i <= n; ++i) + { + T bim1 = (i > 0) ? bm1[static_cast(i - 1)] : T(0); + T bi = (i < n) ? bm1[static_cast(i)] : T(0); + g += coeffs[i] * T(n) * (bim1 - bi); + } + grad[0] = g; + } + else if (tdim == 2) + { + std::vector Bx(static_cast(n1)), + By(static_cast(n1)); + std::vector dBx(static_cast(n1)), + dBy(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + bernstein_1d_deriv(n, xi[0], dBx.data()); + bernstein_1d_deriv(n, xi[1], dBy.data()); + + T g0 = T(0), g1 = T(0); + for (int i = 0; i <= n; ++i) + { + for (int j = 0; j <= n; ++j) + { + T c = coeffs[i * n1 + j]; + g0 += c * dBx[static_cast(i)] + * By[static_cast(j)]; + g1 += c * Bx[static_cast(i)] + * dBy[static_cast(j)]; + } + } + grad[0] = g0; + grad[1] = g1; + } + else if (tdim == 3) + { + std::vector Bx(static_cast(n1)), + By(static_cast(n1)), + Bz(static_cast(n1)); + std::vector dBx(static_cast(n1)), + dBy(static_cast(n1)), + dBz(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + bernstein_1d_all(n, xi[2], Bz.data()); + bernstein_1d_deriv(n, xi[0], dBx.data()); + bernstein_1d_deriv(n, xi[1], dBy.data()); + bernstein_1d_deriv(n, xi[2], dBz.data()); + + T g0 = T(0), g1 = T(0), g2 = T(0); + for (int i = 0; i <= n; ++i) + { + for (int j = 0; j <= n; ++j) + { + for (int k = 0; k <= n; ++k) + { + T c = coeffs[(i * n1 + j) * n1 + k]; + T bx = Bx[static_cast(i)]; + T by = By[static_cast(j)]; + T bz = Bz[static_cast(k)]; + g0 += c * dBx[static_cast(i)] * by * bz; + g1 += c * bx * dBy[static_cast(j)] * bz; + g2 += c * bx * by * dBz[static_cast(k)]; + } + } + } + grad[0] = g0; + grad[1] = g1; + grad[2] = g2; + } +} + +// --------------------------------------------------------------------------- +// Simplex evaluate_all_basis +// --------------------------------------------------------------------------- + +template +void evaluate_all_simplex(int tdim, int degree, const T* xi, T* out) +{ + const int n = degree; + + T lambda[4]; + T sum = T(0); + for (int d = 0; d < tdim; ++d) + sum += xi[d]; + lambda[0] = T(1) - sum; + for (int d = 0; d < tdim; ++d) + lambda[d + 1] = xi[d]; + + int idx = 0; + + if (tdim == 1) + { + for (int i = 0; i <= n; ++i) + { + int alpha[2] = {n - i, i}; + out[idx++] = multinomial(n, alpha, 2) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]); + } + } + else if (tdim == 2) + { + for (int j = 0; j <= n; ++j) + { + for (int i = 0; i <= n - j; ++i) + { + int alpha[3] = {n - i - j, i, j}; + out[idx++] = multinomial(n, alpha, 3) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]); + } + } + } + else if (tdim == 3) + { + for (int k = 0; k <= n; ++k) + { + for (int j = 0; j <= n - k; ++j) + { + for (int i = 0; i <= n - k - j; ++i) + { + int alpha[4] = {n - i - j - k, i, j, k}; + out[idx++] = multinomial(n, alpha, 4) + * integer_pow(lambda[0], alpha[0]) + * integer_pow(lambda[1], alpha[1]) + * integer_pow(lambda[2], alpha[2]) + * integer_pow(lambda[3], alpha[3]); + } + } + } + } +} + +// --------------------------------------------------------------------------- +// Tensor-product evaluate_all_basis +// --------------------------------------------------------------------------- + +template +void evaluate_all_tensor(int tdim, int degree, const T* xi, T* out) +{ + const int n = degree; + const int n1 = n + 1; + + if (tdim == 1) + { + bernstein_1d_all(n, xi[0], out); + } + else if (tdim == 2) + { + std::vector Bx(static_cast(n1)), + By(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + + int idx = 0; + for (int i = 0; i <= n; ++i) + for (int j = 0; j <= n; ++j) + out[idx++] = Bx[static_cast(i)] + * By[static_cast(j)]; + } + else if (tdim == 3) + { + std::vector Bx(static_cast(n1)), + By(static_cast(n1)), + Bz(static_cast(n1)); + bernstein_1d_all(n, xi[0], Bx.data()); + bernstein_1d_all(n, xi[1], By.data()); + bernstein_1d_all(n, xi[2], Bz.data()); + + int idx = 0; + for (int i = 0; i <= n; ++i) + for (int j = 0; j <= n; ++j) + for (int k = 0; k <= n; ++k) + out[idx++] = Bx[static_cast(i)] + * By[static_cast(j)] + * Bz[static_cast(k)]; + } +} + +} // anonymous namespace + +// =========================================================================== +// Public API +// =========================================================================== + +int num_polynomials(cell::type ctype, int degree) +{ + const int n = degree; + switch (ctype) + { + case cell::type::interval: + return n + 1; + case cell::type::triangle: + return (n + 1) * (n + 2) / 2; + case cell::type::tetrahedron: + return (n + 1) * (n + 2) * (n + 3) / 6; + case cell::type::quadrilateral: + return (n + 1) * (n + 1); + case cell::type::hexahedron: + return (n + 1) * (n + 1) * (n + 1); + default: + throw std::invalid_argument( + "bernstein::num_polynomials: unsupported cell type"); + } +} + +// --- evaluate --- + +template +T evaluate(cell::type ctype, int degree, + std::span coeffs, std::span xi) +{ + if (is_simplex(ctype)) + return evaluate_simplex(cell::get_tdim(ctype), degree, + coeffs.data(), xi.data()); + else if (is_tensor_product(ctype)) + return evaluate_tensor(cell::get_tdim(ctype), degree, + coeffs.data(), xi.data()); + else + throw std::invalid_argument("bernstein::evaluate: unsupported cell type"); +} + +template double evaluate(cell::type, int, std::span, std::span); +template float evaluate(cell::type, int, std::span, std::span); + +// --- gradient --- + +template +void gradient(cell::type ctype, int degree, + std::span coeffs, std::span xi, + std::span grad) +{ + if (is_simplex(ctype)) + gradient_simplex(cell::get_tdim(ctype), degree, + coeffs.data(), xi.data(), grad.data()); + else if (is_tensor_product(ctype)) + gradient_tensor(cell::get_tdim(ctype), degree, + coeffs.data(), xi.data(), grad.data()); + else + throw std::invalid_argument("bernstein::gradient: unsupported cell type"); +} + +template void gradient(cell::type, int, std::span, std::span, std::span); +template void gradient(cell::type, int, std::span, std::span, std::span); + +// --- evaluate_basis --- + +template +void evaluate_basis(cell::type ctype, int degree, + std::span xi, std::span out) +{ + if (is_simplex(ctype)) + evaluate_all_simplex(cell::get_tdim(ctype), degree, + xi.data(), out.data()); + else if (is_tensor_product(ctype)) + evaluate_all_tensor(cell::get_tdim(ctype), degree, + xi.data(), out.data()); + else + throw std::invalid_argument("bernstein::evaluate_basis: unsupported cell type"); +} + +template void evaluate_basis(cell::type, int, std::span, std::span); +template void evaluate_basis(cell::type, int, std::span, std::span); + +// --- lagrange_to_bernstein --- + +template +void lagrange_to_bernstein(cell::type ctype, int degree, + std::span ref_points, + std::span nodal_values, + std::vector& coeffs) +{ + const int N = num_polynomials(ctype, degree); + const int tdim = cell::get_tdim(ctype); + const int ndofs = static_cast(nodal_values.size()); + + if (ndofs != N) + throw std::invalid_argument( + "bernstein::lagrange_to_bernstein: nodal_values size (" + + std::to_string(ndofs) + + ") does not match num_polynomials (" + + std::to_string(N) + ")"); + + if (static_cast(ref_points.size()) != ndofs * tdim) + throw std::invalid_argument( + "bernstein::lagrange_to_bernstein: ref_points size mismatch"); + const auto& transform = cached_lagrange_to_bernstein_transform( + ctype, degree, ref_points); + + coeffs.assign(static_cast(N), T(0)); + for (int row = 0; row < N; ++row) + { + T value = T(0); + for (int col = 0; col < N; ++col) + { + value += transform[static_cast(row) * static_cast(N) + + static_cast(col)] + * nodal_values[static_cast(col)]; + } + coeffs[static_cast(row)] = value; + } +} + +template void lagrange_to_bernstein(cell::type, int, + std::span, std::span, + std::vector&); +template void lagrange_to_bernstein(cell::type, int, + std::span, std::span, + std::vector&); + +// --- derivative_coefficients --- + +template +void derivative_coefficients(cell::type ctype, int degree, + std::span coeffs, + int direction, + std::vector& deriv_coeffs) +{ + const int n = degree; + if (n <= 0) + { + deriv_coeffs.clear(); + return; + } + + const int tdim = cell::get_tdim(ctype); + if (direction < 0 || direction >= tdim) + throw std::invalid_argument( + "derivative_coefficients: direction out of range"); + + if (is_simplex(ctype)) + { + // Derivative of degree-n simplex Bernstein → degree-(n-1) expansion. + // c'_beta = n * (c_{beta + e_{dir+1}} - c_{beta + e_0}) + // where beta ranges over multi-indices with |beta| = n-1. + const int nm1 = n - 1; + const int n_out = num_polynomials(ctype, nm1); + deriv_coeffs.resize(static_cast(n_out)); + + if (tdim == 1) + { + // df/dx: c'_i = n * (c_{i+1} - c_i), i = 0..n-1 + for (int i = 0; i <= nm1; ++i) + deriv_coeffs[static_cast(i)] = + T(n) * (coeffs[static_cast(i + 1)] + - coeffs[static_cast(i)]); + } + else if (tdim == 2) + { + int idx = 0; + for (int j = 0; j <= nm1; ++j) + { + for (int i = 0; i <= nm1 - j; ++i, ++idx) + { + int idx_e0 = simplex_index_2d(i, j, n); + int idx_ek; + if (direction == 0) + idx_ek = simplex_index_2d(i + 1, j, n); + else + idx_ek = simplex_index_2d(i, j + 1, n); + + deriv_coeffs[static_cast(idx)] = + T(n) * (coeffs[static_cast(idx_ek)] + - coeffs[static_cast(idx_e0)]); + } + } + } + else if (tdim == 3) + { + int idx = 0; + for (int k = 0; k <= nm1; ++k) + { + for (int j = 0; j <= nm1 - k; ++j) + { + for (int i = 0; i <= nm1 - k - j; ++i, ++idx) + { + int idx_e0 = simplex_index_3d(i, j, k, n); + int idx_ek; + if (direction == 0) + idx_ek = simplex_index_3d(i + 1, j, k, n); + else if (direction == 1) + idx_ek = simplex_index_3d(i, j + 1, k, n); + else + idx_ek = simplex_index_3d(i, j, k + 1, n); + + deriv_coeffs[static_cast(idx)] = + T(n) * (coeffs[static_cast(idx_ek)] + - coeffs[static_cast(idx_e0)]); + } + } + } + } + } + else if (is_tensor_product(ctype)) + { + // Tensor-product: derivative in direction k reduces degree from n to n-1 + // in that direction; degree stays n in other directions. + const int nm1 = n - 1; + const int n1 = n + 1; + + if (tdim == 1) + { + deriv_coeffs.resize(static_cast(n)); + for (int i = 0; i < n; ++i) + deriv_coeffs[static_cast(i)] = + T(n) * (coeffs[static_cast(i + 1)] + - coeffs[static_cast(i)]); + } + else if (tdim == 2) + { + // Index: coeffs[ix * n1 + iy] + if (direction == 0) + { + // d/dx: degree (n-1) in x, n in y → n * (n+1) entries + deriv_coeffs.resize(static_cast(n * n1)); + for (int ix = 0; ix < n; ++ix) + for (int iy = 0; iy <= n; ++iy) + deriv_coeffs[static_cast(ix * n1 + iy)] = + T(n) * (coeffs[static_cast((ix + 1) * n1 + iy)] + - coeffs[static_cast(ix * n1 + iy)]); + } + else + { + // d/dy: degree n in x, (n-1) in y → (n+1) * n entries + deriv_coeffs.resize(static_cast(n1 * n)); + for (int ix = 0; ix <= n; ++ix) + for (int iy = 0; iy < n; ++iy) + deriv_coeffs[static_cast(ix * n + iy)] = + T(n) * (coeffs[static_cast(ix * n1 + iy + 1)] + - coeffs[static_cast(ix * n1 + iy)]); + } + } + else if (tdim == 3) + { + // Index: coeffs[(ix * n1 + iy) * n1 + iz] + if (direction == 0) + { + deriv_coeffs.resize(static_cast(n * n1 * n1)); + for (int ix = 0; ix < n; ++ix) + for (int iy = 0; iy <= n; ++iy) + for (int iz = 0; iz <= n; ++iz) + deriv_coeffs[static_cast((ix * n1 + iy) * n1 + iz)] = + T(n) * (coeffs[static_cast(((ix + 1) * n1 + iy) * n1 + iz)] + - coeffs[static_cast((ix * n1 + iy) * n1 + iz)]); + } + else if (direction == 1) + { + deriv_coeffs.resize(static_cast(n1 * n * n1)); + for (int ix = 0; ix <= n; ++ix) + for (int iy = 0; iy < n; ++iy) + for (int iz = 0; iz <= n; ++iz) + deriv_coeffs[static_cast((ix * n + iy) * n1 + iz)] = + T(n) * (coeffs[static_cast((ix * n1 + iy + 1) * n1 + iz)] + - coeffs[static_cast((ix * n1 + iy) * n1 + iz)]); + } + else + { + deriv_coeffs.resize(static_cast(n1 * n1 * n)); + for (int ix = 0; ix <= n; ++ix) + for (int iy = 0; iy <= n; ++iy) + for (int iz = 0; iz < n; ++iz) + deriv_coeffs[static_cast((ix * n1 + iy) * n + iz)] = + T(n) * (coeffs[static_cast((ix * n1 + iy) * n1 + iz + 1)] + - coeffs[static_cast((ix * n1 + iy) * n1 + iz)]); + } + } + } + else + { + throw std::invalid_argument( + "derivative_coefficients: unsupported cell type"); + } +} + +template void derivative_coefficients(cell::type, int, + std::span, int, + std::vector&); +template void derivative_coefficients(cell::type, int, + std::span, int, + std::vector&); + +} // namespace cutcells::bernstein diff --git a/cpp/src/bernstein.h b/cpp/src/bernstein.h new file mode 100644 index 0000000..b688b49 --- /dev/null +++ b/cpp/src/bernstein.h @@ -0,0 +1,115 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +#include "cell_types.h" + +namespace cutcells::bernstein +{ + +/// Number of Bernstein basis functions for a given cell type and degree. +/// +/// Simplex cells (interval, triangle, tetrahedron): C(n+d, d) +/// Tensor-product cells (quadrilateral, hexahedron): (n+1)^d +int num_polynomials(cell::type ctype, int degree); + +/// Evaluate a Bernstein expansion at a point xi in reference coordinates. +/// +/// f(xi) = sum_i coeffs[i] * B_i(xi) +/// +/// @param ctype Cell type +/// @param degree Polynomial degree +/// @param coeffs Bernstein coefficients (size = num_polynomials(ctype, degree)) +/// @param xi Point in reference coordinates (size = tdim) +/// @return Value of the expansion at xi +template +T evaluate(cell::type ctype, int degree, + std::span coeffs, std::span xi); + +/// Gradient of a Bernstein expansion at xi in reference coordinates. +/// +/// grad[k] = d/d(xi_k) sum_i coeffs[i] * B_i(xi) +/// +/// @param ctype Cell type +/// @param degree Polynomial degree +/// @param coeffs Bernstein coefficients (size = num_polynomials(ctype, degree)) +/// @param xi Point in reference coordinates (size = tdim) +/// @param[out] grad Gradient vector (size = tdim) +template +void gradient(cell::type ctype, int degree, + std::span coeffs, std::span xi, + std::span grad); + +/// Evaluate all N basis functions at a point. +/// +/// out[i] = B_i(xi) for i = 0, ..., N-1 +/// +/// @param ctype Cell type +/// @param degree Polynomial degree +/// @param xi Point in reference coordinates (size = tdim) +/// @param[out] out Basis function values (size = num_polynomials(ctype, degree)) +template +void evaluate_basis(cell::type ctype, int degree, + std::span xi, std::span out); + +/// Convert Lagrange nodal values to Bernstein coefficients. +/// +/// Builds the Bernstein evaluation matrix V (V_{ij} = B_j(ref_point_i)), +/// then solves V * coeffs = nodal_values. +/// +/// @param ctype Cell type +/// @param degree Polynomial degree +/// @param ref_points Reference coordinates of Lagrange nodes (flat: ndofs * tdim) +/// @param nodal_values Function values at Lagrange nodes (ndofs) +/// @param[out] coeffs Bernstein coefficients (resized to num_polynomials) +template +void lagrange_to_bernstein(cell::type ctype, int degree, + std::span ref_points, + std::span nodal_values, + std::vector& coeffs); + +/// Compute Bernstein coefficients of the partial derivative d/d(xi_direction) +/// of a degree-n polynomial on a simplex or tensor-product cell. +/// +/// For simplex cells the result is a degree-(n-1) Bernstein expansion with +/// coefficients c'_beta = n * (c_{beta + e_{dir+1}} - c_{beta + e_0}). +/// +/// For tensor-product cells the result is a degree-(n-1) expansion in the +/// differentiated direction and degree-n in the others. +/// +/// @param ctype Cell type (interval, triangle, tetrahedron, quad, hex). +/// @param degree Polynomial degree of the input. +/// @param coeffs Bernstein coefficients (size = num_polynomials(ctype, degree)). +/// @param direction Coordinate direction 0..tdim-1. +/// @param[out] deriv_coeffs Derivative Bernstein coefficients. +template +void derivative_coefficients(cell::type ctype, int degree, + std::span coeffs, + int direction, + std::vector& deriv_coeffs); + +// ---- Helper utilities exposed for testing ---- + +/// True for interval, triangle, tetrahedron. +inline bool is_simplex(cell::type ct) +{ + return ct == cell::type::interval + || ct == cell::type::triangle + || ct == cell::type::tetrahedron; +} + +/// True for quadrilateral, hexahedron. +inline bool is_tensor_product(cell::type ct) +{ + return ct == cell::type::quadrilateral + || ct == cell::type::hexahedron; +} + +} // namespace cutcells::bernstein diff --git a/cpp/src/cell_certification.cpp b/cpp/src/cell_certification.cpp new file mode 100644 index 0000000..cf8a496 --- /dev/null +++ b/cpp/src/cell_certification.cpp @@ -0,0 +1,2140 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "cell_certification.h" +#include "bernstein.h" +#include "cell_flags.h" +#include "cell_topology.h" +#include "cut_cell.h" +#include "cut_tetrahedron.h" +#include "cut_triangle.h" +#include "edge_certification.h" +#include "mapping.h" +#include "reference_cell.h" +#include "refine_cell.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +template +void set_vertex_sign_for_level_set(AdaptCell& adapt_cell, + int vertex_id, + int level_set_id, + T value, + T zero_tol) +{ + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + auto& zero_mask = adapt_cell.zero_mask_per_vertex[static_cast(vertex_id)]; + auto& negative_mask = adapt_cell.negative_mask_per_vertex[static_cast(vertex_id)]; + + zero_mask &= ~bit; + negative_mask &= ~bit; + + if (std::fabs(value) <= zero_tol) + zero_mask |= bit; + else if (value < T(0)) + negative_mask |= bit; +} + +template +std::map, int> build_leaf_edge_lookup(const AdaptCell& adapt_cell) +{ + std::map, int> edge_lookup; + const int n_edges = adapt_cell.n_entities(1); + for (int e = 0; e < n_edges; ++e) + { + auto ev = adapt_cell.entity_to_vertex[1][static_cast(e)]; + edge_lookup[{std::min(static_cast(ev[0]), static_cast(ev[1])), + std::max(static_cast(ev[0]), static_cast(ev[1]))}] = e; + } + return edge_lookup; +} + +void append_top_cell_local(std::vector& types, + EntityAdjacency& adj, + cell::type ctype, + std::span verts) +{ + types.push_back(ctype); + for (int v : verts) + adj.indices.push_back(static_cast(v)); + adj.offsets.push_back(static_cast(adj.indices.size())); +} + +int basix_edge_id_for_vertices(cell::type cell_type, int a, int b) +{ + const auto ledges = cell::edges(cell_type); + for (std::size_t e = 0; e < ledges.size(); ++e) + { + const auto edge = ledges[e]; + if ((edge[0] == a && edge[1] == b) || (edge[0] == b && edge[1] == a)) + return static_cast(e); + } + + throw std::runtime_error("basix_edge_id_for_vertices: edge not found"); +} + +template +std::vector canonicalize_cut_part_cell_vertices( + const cell::CutCell& part, + int part_cell_id, + cell::type leaf_cell_type) +{ + auto part_vertices = cell::cell_vertices(part, part_cell_id); + std::vector canonical(part_vertices.begin(), part_vertices.end()); + + const cell::type part_cell_type = + part._types[static_cast(part_cell_id)]; + + if (leaf_cell_type == cell::type::triangle + && part_cell_type == cell::type::quadrilateral) + { + std::map token_to_part_vertex; + std::array present = {false, false, false}; + + for (int part_lv : part_vertices) + { + const int token = cell::vtk_parent_entity_token_to_basix( + leaf_cell_type, + part._vertex_parent_entity[static_cast(part_lv)]); + token_to_part_vertex[token] = part_lv; + if (token >= 100 && token < 103) + present[static_cast(token - 100)] = true; + } + + int omitted = -1; + for (int v = 0; v < 3; ++v) + { + if (!present[static_cast(v)]) + { + omitted = v; + break; + } + } + if (omitted < 0) + throw std::runtime_error("canonicalize_cut_part_cell_vertices: invalid triangle quad"); + + const auto kept_edge = cell::edges(cell::type::triangle)[static_cast(omitted)]; + const int a = kept_edge[0]; + const int b = kept_edge[1]; + const int e_a = basix_edge_id_for_vertices(cell::type::triangle, a, omitted); + const int e_b = basix_edge_id_for_vertices(cell::type::triangle, b, omitted); + + canonical = { + token_to_part_vertex.contains(100 + a) ? token_to_part_vertex.at(100 + a) + : throw std::runtime_error("canonicalize_cut_part_cell_vertices: missing triangle vertex token a"), + token_to_part_vertex.contains(100 + b) ? token_to_part_vertex.at(100 + b) + : throw std::runtime_error("canonicalize_cut_part_cell_vertices: missing triangle vertex token b"), + token_to_part_vertex.contains(e_a) ? token_to_part_vertex.at(e_a) + : throw std::runtime_error("canonicalize_cut_part_cell_vertices: missing triangle edge token ea"), + token_to_part_vertex.contains(e_b) ? token_to_part_vertex.at(e_b) + : throw std::runtime_error("canonicalize_cut_part_cell_vertices: missing triangle edge token eb")}; + } + else if (leaf_cell_type == cell::type::tetrahedron + && part_cell_type == cell::type::prism) + { + std::map token_to_part_vertex; + std::array present = {false, false, false, false}; + + for (int part_lv : part_vertices) + { + const int token = cell::vtk_parent_entity_token_to_basix( + leaf_cell_type, + part._vertex_parent_entity[static_cast(part_lv)]); + token_to_part_vertex[token] = part_lv; + if (token >= 100 && token < 104) + present[static_cast(token - 100)] = true; + } + + std::vector present_vertices; + std::vector missing_vertices; + for (int v = 0; v < 4; ++v) + { + if (present[static_cast(v)]) + present_vertices.push_back(v); + else + missing_vertices.push_back(v); + } + + if (present_vertices.size() == 3) + { + const int omitted = missing_vertices.front(); + const auto face = cell::tet_face(omitted); + canonical.resize(6); + for (int i = 0; i < 3; ++i) + { + const int fv = face[static_cast(i)]; + const int edge_id = basix_edge_id_for_vertices( + cell::type::tetrahedron, fv, omitted); + auto v_it = token_to_part_vertex.find(100 + fv); + auto e_it = token_to_part_vertex.find(edge_id); + if (v_it == token_to_part_vertex.end() || e_it == token_to_part_vertex.end()) + { + throw std::runtime_error( + "canonicalize_cut_part_cell_vertices: invalid 3-vertex prism token set"); + } + canonical[static_cast(i)] = v_it->second; + canonical[static_cast(3 + i)] = e_it->second; + } + } + else if (present_vertices.size() == 2) + { + const int p = present_vertices[0]; + const int q = present_vertices[1]; + const int r = missing_vertices[0]; + const int s = missing_vertices[1]; + + const int e_pr = basix_edge_id_for_vertices(cell::type::tetrahedron, p, r); + const int e_ps = basix_edge_id_for_vertices(cell::type::tetrahedron, p, s); + const int e_qr = basix_edge_id_for_vertices(cell::type::tetrahedron, q, r); + const int e_qs = basix_edge_id_for_vertices(cell::type::tetrahedron, q, s); + + const std::array prism_tokens = { + 100 + p, e_pr, e_ps, + 100 + q, e_qr, e_qs}; + canonical.resize(6); + for (int i = 0; i < 6; ++i) + { + auto it = token_to_part_vertex.find(prism_tokens[static_cast(i)]); + if (it == token_to_part_vertex.end()) + { + throw std::runtime_error( + "canonicalize_cut_part_cell_vertices: invalid 2-vertex prism token set"); + } + canonical[static_cast(i)] = it->second; + } + } + else + { + throw std::runtime_error( + "canonicalize_cut_part_cell_vertices: unsupported tetra prism token pattern"); + } + } + + return canonical; +} + +template +std::vector gather_leaf_cell_vertex_level_set_values( + const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int cell_id) +{ + const int tdim = adapt_cell.tdim; + auto cell_verts = adapt_cell.entity_to_vertex[tdim][static_cast(cell_id)]; + std::vector values(cell_verts.size(), T(0)); + + for (std::size_t j = 0; j < cell_verts.size(); ++j) + { + const int gv = static_cast(cell_verts[j]); + std::span xi( + adapt_cell.vertex_coords.data() + + static_cast(gv) * static_cast(tdim), + static_cast(tdim)); + values[j] = ls_cell.value(xi); + } + + return values; +} + +template +int append_vertex_with_parent_info(AdaptCell& adapt_cell, + std::span coords, + int parent_dim, + int parent_id, + std::span parent_param, + int source_edge_id) +{ + const int new_v = adapt_cell.n_vertices(); + adapt_cell.vertex_coords.insert(adapt_cell.vertex_coords.end(), + coords.begin(), coords.end()); + adapt_cell.vertex_parent_dim.push_back(static_cast(parent_dim)); + adapt_cell.vertex_parent_id.push_back(parent_id); + const std::int32_t param_offset = + static_cast(adapt_cell.vertex_parent_param.size()); + adapt_cell.vertex_parent_param.insert(adapt_cell.vertex_parent_param.end(), + parent_param.begin(), + parent_param.end()); + adapt_cell.vertex_parent_param_offset.push_back( + param_offset + static_cast(parent_param.size())); + adapt_cell.zero_mask_per_vertex.push_back(0); + adapt_cell.negative_mask_per_vertex.push_back(0); + adapt_cell.vertex_source_edge_id.push_back(source_edge_id); + return new_v; +} + +template +void inherit_common_edge_sign_masks(AdaptCell& adapt_cell, + int vertex_id, + int edge_id) +{ + if (edge_id < 0) + return; + + auto edge_vertices = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + if (edge_vertices.size() != 2) + return; + + const auto v0 = static_cast(edge_vertices[0]); + const auto v1 = static_cast(edge_vertices[1]); + const std::uint64_t common_zero = + adapt_cell.zero_mask_per_vertex[v0] & adapt_cell.zero_mask_per_vertex[v1]; + const std::uint64_t common_negative = + (adapt_cell.negative_mask_per_vertex[v0] + & adapt_cell.negative_mask_per_vertex[v1]) + & ~common_zero; + + auto& zero_mask = adapt_cell.zero_mask_per_vertex[static_cast(vertex_id)]; + auto& negative_mask = adapt_cell.negative_mask_per_vertex[static_cast(vertex_id)]; + zero_mask |= common_zero; + negative_mask &= ~common_zero; + negative_mask |= common_negative; +} + +template +void gather_adapt_edge_bernstein(const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int edge_id, + std::vector& edge_coeffs) +{ + auto verts = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + const int tdim = adapt_cell.tdim; + std::span xi_a( + adapt_cell.vertex_coords.data() + + static_cast(verts[0]) * static_cast(tdim), + static_cast(tdim)); + std::span xi_b( + adapt_cell.vertex_coords.data() + + static_cast(verts[1]) * static_cast(tdim), + static_cast(tdim)); + + restrict_edge_bernstein_exact( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), + xi_a, xi_b, edge_coeffs); +} + +template +bool linear_one_root_parameter_from_endpoint_values(T phi0, + T phi1, + T zero_tol, + T& root_t) +{ + const bool left_zero = std::fabs(phi0) <= zero_tol; + const bool right_zero = std::fabs(phi1) <= zero_tol; + + root_t = T(0); + if (left_zero && right_zero) + return false; + if (left_zero) + return true; + if (right_zero) + { + root_t = T(1); + return true; + } + + const bool brackets_zero = (phi0 < T(0) && phi1 > T(0)) + || (phi0 > T(0) && phi1 < T(0)); + if (!brackets_zero) + return false; + + const T denom = phi0 - phi1; + const T denom_tol = + std::max(zero_tol, T(64) * std::numeric_limits::epsilon()); + if (std::fabs(denom) <= denom_tol) + return false; + + root_t = std::clamp(phi0 / denom, T(0), T(1)); + return true; +} + +/// Returns true if the Bernstein expansion has a sign-definite partial +/// derivative in at least one reference direction on the subcell. +/// A monotone function with no boundary root cannot have an interior zero. +template +bool has_monotone_direction(cell::type subcell_type, int degree, + std::span subcell_coeffs, T sign_tol) +{ + const int tdim = cell::get_tdim(subcell_type); + std::vector deriv_coeffs; + for (int dir = 0; dir < tdim; ++dir) + { + bernstein::derivative_coefficients(subcell_type, degree, + subcell_coeffs, dir, deriv_coeffs); + if (deriv_coeffs.empty()) + continue; + std::span dc(deriv_coeffs); + if (bernstein_all_positive(dc, sign_tol) + || bernstein_all_negative(dc, sign_tol)) + { + return true; + } + } + return false; +} + +/// Check whether any edge incident to cell_id has a root (one_root, multiple_roots, or zero). +template +bool cell_has_any_edge_root(const AdaptCell& adapt_cell, + int level_set_id, int cell_id) +{ + const int tdim = adapt_cell.tdim; + auto cell_verts = adapt_cell.entity_to_vertex[tdim][static_cast(cell_id)]; + const int n_edges = adapt_cell.n_entities(1); + + for (int e = 0; e < n_edges; ++e) + { + auto ev = adapt_cell.entity_to_vertex[1][static_cast(e)]; + bool v0_in = false, v1_in = false; + for (auto cv : cell_verts) + { + if (cv == ev[0]) v0_in = true; + if (cv == ev[1]) v1_in = true; + } + if (!v0_in || !v1_in) + continue; + + const EdgeRootTag etag = adapt_cell.get_edge_root_tag(level_set_id, e); + if (etag == EdgeRootTag::one_root + || etag == EdgeRootTag::multiple_roots + || etag == EdgeRootTag::zero) + { + return true; + } + } + return false; +} + +template +bool vertex_parameter_on_parent_edge(const AdaptCell& adapt_cell, + int vertex_id, + int parent_edge_id, + T& t) +{ + const int dim = adapt_cell.vertex_parent_dim[static_cast(vertex_id)]; + if (dim == 0) + { + const auto parent_edge = + cell::edges(adapt_cell.parent_cell_type)[static_cast(parent_edge_id)]; + if (parent_edge[0] == vertex_id) + { + t = T(0); + return true; + } + if (parent_edge[1] == vertex_id) + { + t = T(1); + return true; + } + return false; + } + + if (dim != 1) + return false; + if (adapt_cell.vertex_parent_id[static_cast(vertex_id)] != parent_edge_id) + return false; + + const auto begin = + static_cast(adapt_cell.vertex_parent_param_offset[static_cast(vertex_id)]); + const auto end = + static_cast(adapt_cell.vertex_parent_param_offset[static_cast(vertex_id + 1)]); + if (end - begin != 1) + return false; + + t = adapt_cell.vertex_parent_param[begin]; + return true; +} + +template +int ensure_one_root_vertex_on_edge(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int edge_id, + T zero_tol, + T sign_tol, + int edge_max_depth) +{ + const int n_edges = adapt_cell.n_entities(1); + if (adapt_cell.edge_one_root_has_value.size() + < static_cast((level_set_id + 1) * n_edges)) + { + adapt_cell.resize_one_root_data(level_set_id + 1); + } + + const auto idx = static_cast(level_set_id * n_edges + edge_id); + if (adapt_cell.edge_one_root_vertex_id[idx] >= 0) + return adapt_cell.edge_one_root_vertex_id[idx]; + + if (adapt_cell.get_edge_root_tag(level_set_id, edge_id) != EdgeRootTag::one_root) + return -1; + + (void)sign_tol; + (void)edge_max_depth; + + T root_t = T(0); + bool has_linear_root = adapt_cell.edge_one_root_has_value[idx] != 0; + if (has_linear_root) + { + root_t = adapt_cell.edge_one_root_param[idx]; + } + else + { + std::vector edge_coeffs; + gather_adapt_edge_bernstein(adapt_cell, ls_cell, edge_id, edge_coeffs); + has_linear_root = + edge_coeffs.size() >= 2 + && linear_one_root_parameter_from_endpoint_values( + edge_coeffs.front(), edge_coeffs.back(), zero_tol, root_t); + } + + if (!has_linear_root) + { + throw std::runtime_error( + "ensure_one_root_vertex_on_edge: failed to compute linear edge intersection"); + } + + auto verts = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + const int v0 = static_cast(verts[0]); + const int v1 = static_cast(verts[1]); + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + const T endpoint_tol = std::max(zero_tol, T(64) * std::numeric_limits::epsilon()); + if (std::fabs(root_t) <= endpoint_tol + && (adapt_cell.zero_mask_per_vertex[static_cast(v0)] & bit) != 0) + { + adapt_cell.edge_one_root_param[idx] = T(0); + adapt_cell.edge_one_root_vertex_id[idx] = v0; + adapt_cell.edge_one_root_has_value[idx] = 1; + return v0; + } + if (std::fabs(root_t - T(1)) <= endpoint_tol + && (adapt_cell.zero_mask_per_vertex[static_cast(v1)] & bit) != 0) + { + adapt_cell.edge_one_root_param[idx] = T(1); + adapt_cell.edge_one_root_vertex_id[idx] = v1; + adapt_cell.edge_one_root_has_value[idx] = 1; + return v1; + } + + const int tdim = adapt_cell.tdim; + std::vector coords(static_cast(tdim), T(0)); + for (int d = 0; d < tdim; ++d) + { + const T x0 = adapt_cell.vertex_coords[static_cast(v0 * tdim + d)]; + const T x1 = adapt_cell.vertex_coords[static_cast(v1 * tdim + d)]; + coords[static_cast(d)] = (T(1) - root_t) * x0 + root_t * x1; + } + + int parent_dim = adapt_cell.tdim; + int parent_id = adapt_cell.parent_cell_id; + std::vector parent_param(coords.begin(), coords.end()); + + int parent_edge_id = -1; + T parent_t0 = T(0); + T parent_t1 = T(1); + if (edge_is_on_single_parent_edge(adapt_cell, edge_id, parent_edge_id) + && vertex_parameter_on_parent_edge(adapt_cell, v0, parent_edge_id, parent_t0) + && vertex_parameter_on_parent_edge(adapt_cell, v1, parent_edge_id, parent_t1)) + { + parent_dim = 1; + parent_id = parent_edge_id; + parent_param.assign( + 1, (T(1) - root_t) * parent_t0 + root_t * parent_t1); + } + + const int vertex_id = append_vertex_with_parent_info( + adapt_cell, + std::span(coords), + parent_dim, + parent_id, + std::span(parent_param), + edge_id); + + inherit_common_edge_sign_masks(adapt_cell, vertex_id, edge_id); + set_vertex_sign_for_level_set(adapt_cell, vertex_id, level_set_id, T(0), zero_tol); + adapt_cell.edge_one_root_param[idx] = root_t; + adapt_cell.edge_one_root_vertex_id[idx] = vertex_id; + adapt_cell.edge_one_root_has_value[idx] = 1; + return vertex_id; +} + +template +void append_ready_cut_part_cells( + AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int old_cell_id, + const cell::CutCell& part, + cell::type leaf_cell_type, + std::span old_cell_vertices, + std::span old_edge_ids_by_local_edge, + std::vector& new_types, + EntityAdjacency& new_cells, + std::vector& old_cell_ids_for_new_cells, + std::vector& source_cell_ids_for_new_cells, + std::vector& refinement_reasons_for_new_cells, + std::vector& explicit_current_ls_tags, + std::map& token_to_vertex, + T zero_tol, + CellCertTag side_tag) +{ + const int parent_tdim = adapt_cell.tdim; + + for (std::size_t lv = 0; lv < part._vertex_parent_entity.size(); ++lv) + { + const int raw_token = part._vertex_parent_entity[lv]; + const int token = cell::vtk_parent_entity_token_to_basix(leaf_cell_type, raw_token); + if (token_to_vertex.contains(token)) + continue; + + int vertex_id = -1; + if (token >= 100 && token < 200) + { + const int local_vid = token - 100; + vertex_id = old_cell_vertices[static_cast(local_vid)]; + } + else + { + std::span x( + part._vertex_coords.data() + + lv * static_cast(parent_tdim), + static_cast(parent_tdim)); + + int parent_dim = parent_tdim; + int parent_id = -1; + std::vector parent_param; + + int old_edge_id_for_token = -1; + if (token >= 0 && token < 100) + { + const int local_edge_id = token; + if (local_edge_id >= 0 + && local_edge_id < static_cast(old_edge_ids_by_local_edge.size())) + { + const int old_edge_id = old_edge_ids_by_local_edge[static_cast(local_edge_id)]; + old_edge_id_for_token = old_edge_id; + int parent_edge_id = -1; + if (old_edge_id >= 0 + && edge_is_on_single_parent_edge(adapt_cell, old_edge_id, parent_edge_id)) + { + parent_dim = 1; + parent_id = parent_edge_id; + + auto edge_vertices = + adapt_cell.entity_to_vertex[1][static_cast(old_edge_id)]; + T parent_t0 = T(0); + T parent_t1 = T(1); + if (edge_vertices.size() == 2 + && vertex_parameter_on_parent_edge( + adapt_cell, edge_vertices[0], parent_edge_id, parent_t0) + && vertex_parameter_on_parent_edge( + adapt_cell, edge_vertices[1], parent_edge_id, parent_t1)) + { + parent_param.assign(1, T(0.5) * (parent_t0 + parent_t1)); + } + } + } + } + if (parent_dim == parent_tdim && parent_id < 0) + { + parent_id = adapt_cell.parent_cell_id; + parent_param.assign(x.begin(), x.end()); + } + + vertex_id = append_vertex_with_parent_info( + adapt_cell, x, parent_dim, parent_id, + std::span(parent_param), + old_edge_id_for_token); + + if (token >= 0 && token < 100) + { + const int local_edge_id = token; + if (old_edge_id_for_token >= 0) + inherit_common_edge_sign_masks(adapt_cell, vertex_id, old_edge_id_for_token); + + const EdgeRootTag edge_tag = + old_edge_id_for_token >= 0 + ? adapt_cell.get_edge_root_tag(level_set_id, old_edge_id_for_token) + : EdgeRootTag::not_classified; + const bool is_level_set_root = + local_edge_id >= 0 + && local_edge_id < static_cast(old_edge_ids_by_local_edge.size()) + && edge_tag == EdgeRootTag::one_root; + const bool is_level_set_zero_edge = + local_edge_id >= 0 + && local_edge_id < static_cast(old_edge_ids_by_local_edge.size()) + && edge_tag == EdgeRootTag::zero; + + if (is_level_set_root || is_level_set_zero_edge) + { + // Straight cut vertices lie on the straight interface by + // construction. They should participate in phi=0 + // extraction even when the polynomial level set evaluated at + // this affine point is not exactly zero. + set_vertex_sign_for_level_set( + adapt_cell, vertex_id, level_set_id, T(0), zero_tol); + } + else + { + // LUT triangulation may insert midpoints on uncut parent + // edges. They inherit parent-edge provenance, but are not + // zero vertices for this level set. + const T value = ls_cell.value(x); + set_vertex_sign_for_level_set( + adapt_cell, vertex_id, level_set_id, value, zero_tol); + } + } + else + { + const T value = ls_cell.value(x); + set_vertex_sign_for_level_set( + adapt_cell, vertex_id, level_set_id, value, zero_tol); + } + } + + token_to_vertex[token] = vertex_id; + } + + const int n_part_cells = cell::num_cells(part); + for (int pc = 0; pc < n_part_cells; ++pc) + { + const std::vector part_vertices = + canonicalize_cut_part_cell_vertices(part, pc, leaf_cell_type); + std::vector mapped(part_vertices.size(), -1); + for (std::size_t j = 0; j < part_vertices.size(); ++j) + { + const int part_lv = part_vertices[j]; + const int raw_token = part._vertex_parent_entity[static_cast(part_lv)]; + const int token = cell::vtk_parent_entity_token_to_basix(leaf_cell_type, raw_token); + auto token_it = token_to_vertex.find(token); + if (token_it == token_to_vertex.end()) + { + throw std::runtime_error( + "append_ready_cut_part_cells: missing mapped token " + + std::to_string(token) + " raw_token=" + std::to_string(raw_token) + + " leaf_type=" + cell_type_to_str(leaf_cell_type) + + " part_cell_type=" + + cell_type_to_str(part._types[static_cast(pc)])); + } + mapped[j] = token_it->second; + } + + append_top_cell_local(new_types, new_cells, + part._types[static_cast(pc)], + std::span(mapped)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(old_cell_id); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::cut_level_set); + explicit_current_ls_tags.push_back(side_tag); + } +} + +template +CellCertTag classify_ready_to_cut_topology(const AdaptCell& adapt_cell, + int level_set_id, + int cell_id) +{ + const int tdim = adapt_cell.tdim; + auto cell_verts = adapt_cell.entity_to_vertex[tdim][static_cast(cell_id)]; + const cell::type subcell_type = adapt_cell.entity_types[tdim][static_cast(cell_id)]; + + std::set cut_point_tokens; + bool has_multiple_roots = false; + bool has_zero_edge = false; + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + + std::map local_vertex_by_global; + for (std::size_t lv = 0; lv < cell_verts.size(); ++lv) + { + const int gv = static_cast(cell_verts[lv]); + local_vertex_by_global[gv] = static_cast(lv); + if ((adapt_cell.zero_mask_per_vertex[static_cast(gv)] & bit) != 0) + cut_point_tokens.insert(100 + static_cast(lv)); + } + + const int n_edges = adapt_cell.n_entities(1); + for (int e = 0; e < n_edges; ++e) + { + auto edge_verts = adapt_cell.entity_to_vertex[1][static_cast(e)]; + auto it0 = local_vertex_by_global.find(static_cast(edge_verts[0])); + auto it1 = local_vertex_by_global.find(static_cast(edge_verts[1])); + if (it0 == local_vertex_by_global.end() || it1 == local_vertex_by_global.end()) + continue; + + const EdgeRootTag etag = adapt_cell.get_edge_root_tag(level_set_id, e); + if (etag == EdgeRootTag::multiple_roots) + { + has_multiple_roots = true; + } + else if (etag == EdgeRootTag::zero) + { + has_zero_edge = true; + } + else if (etag == EdgeRootTag::one_root) + { + const bool v0_zero = + (adapt_cell.zero_mask_per_vertex[static_cast(edge_verts[0])] & bit) != 0; + const bool v1_zero = + (adapt_cell.zero_mask_per_vertex[static_cast(edge_verts[1])] & bit) != 0; + + if (v0_zero && !v1_zero) + cut_point_tokens.insert(100 + it0->second); + else if (v1_zero && !v0_zero) + cut_point_tokens.insert(100 + it1->second); + else if (v0_zero && v1_zero) + has_zero_edge = true; + else + { + const int local_edge_id = basix_edge_id_for_vertices( + subcell_type, it0->second, it1->second); + cut_point_tokens.insert(local_edge_id); + } + } + } + + if (has_multiple_roots) + return CellCertTag::cut; + + if (subcell_type == cell::type::triangle && cut_point_tokens.size() == 2) + return CellCertTag::ready_to_cut; + + if (subcell_type == cell::type::tetrahedron + && (cut_point_tokens.size() == 3 || cut_point_tokens.size() == 4)) + { + return CellCertTag::ready_to_cut; + } + + if (!cut_point_tokens.empty()) + return CellCertTag::cut; + + if (has_zero_edge) + return CellCertTag::not_classified; + + return CellCertTag::not_classified; +} + +/// Check whether any edge incident to face_id has a root (one_root, multiple_roots, or zero). +template +bool face_has_any_edge_root(const AdaptCell& adapt_cell, + int level_set_id, int face_id) +{ + auto face_verts = adapt_cell.entity_to_vertex[2][static_cast(face_id)]; + const int n_edges = adapt_cell.n_entities(1); + + for (int e = 0; e < n_edges; ++e) + { + auto ev = adapt_cell.entity_to_vertex[1][static_cast(e)]; + bool v0_in = false, v1_in = false; + for (auto fv : face_verts) + { + if (fv == ev[0]) v0_in = true; + if (fv == ev[1]) v1_in = true; + } + if (!v0_in || !v1_in) + continue; + + const EdgeRootTag etag = adapt_cell.get_edge_root_tag(level_set_id, e); + if (etag == EdgeRootTag::one_root + || etag == EdgeRootTag::multiple_roots + || etag == EdgeRootTag::zero) + { + return true; + } + } + return false; +} + +} // anonymous namespace + +// ===================================================================== +// restrict_subcell_bernstein_exact +// ===================================================================== + +template +void restrict_subcell_bernstein_exact(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + cell::type subcell_type, + std::span subcell_vertex_coords, + std::vector& subcell_coeffs) +{ + // Strategy: generate reference points in the subcell, map them to the + // parent reference frame using the subcell vertex coordinates as an + // affine map, evaluate the parent Bernstein at each mapped point, + // then convert from nodal values to Bernstein on the subcell. + + const int subcell_tdim = cell::get_tdim(subcell_type); + const int parent_tdim = cell::get_tdim(parent_cell_type); + const int n_dofs = bernstein::num_polynomials(subcell_type, degree); + + // Number of subcell vertices. + int n_subcell_verts = 0; + switch (subcell_type) + { + case cell::type::interval: n_subcell_verts = 2; break; + case cell::type::triangle: n_subcell_verts = 3; break; + case cell::type::tetrahedron: n_subcell_verts = 4; break; + case cell::type::quadrilateral: n_subcell_verts = 4; break; + case cell::type::hexahedron: n_subcell_verts = 8; break; + default: + throw std::runtime_error( + "restrict_subcell_bernstein_exact: unsupported subcell type"); + } + + // Generate equispaced reference points on the subcell. + // For simplices: use lattice of order = degree. + // For tensor-product: use tensor-product grid of order = degree. + // These serve as Lagrange interpolation nodes. + std::vector ref_points_flat(static_cast(n_dofs * subcell_tdim)); + + if (bernstein::is_simplex(subcell_type)) + { + // Equispaced lattice on the reference simplex. + int idx = 0; + if (subcell_tdim == 1) + { + for (int i = 0; i <= degree; ++i, ++idx) + ref_points_flat[static_cast(idx)] = + (degree > 0) ? T(i) / T(degree) : T(0); + } + else if (subcell_tdim == 2) + { + for (int j = 0; j <= degree; ++j) + for (int i = 0; i <= degree - j; ++i, ++idx) + { + ref_points_flat[static_cast(idx * 2)] = + (degree > 0) ? T(i) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 2 + 1)] = + (degree > 0) ? T(j) / T(degree) : T(0); + } + } + else if (subcell_tdim == 3) + { + for (int k = 0; k <= degree; ++k) + for (int j = 0; j <= degree - k; ++j) + for (int i = 0; i <= degree - k - j; ++i, ++idx) + { + ref_points_flat[static_cast(idx * 3)] = + (degree > 0) ? T(i) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 3 + 1)] = + (degree > 0) ? T(j) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 3 + 2)] = + (degree > 0) ? T(k) / T(degree) : T(0); + } + } + } + else // tensor-product + { + int idx = 0; + if (subcell_tdim == 2) + { + for (int iy = 0; iy <= degree; ++iy) + for (int ix = 0; ix <= degree; ++ix, ++idx) + { + ref_points_flat[static_cast(idx * 2)] = + (degree > 0) ? T(ix) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 2 + 1)] = + (degree > 0) ? T(iy) / T(degree) : T(0); + } + } + else if (subcell_tdim == 3) + { + for (int iz = 0; iz <= degree; ++iz) + for (int iy = 0; iy <= degree; ++iy) + for (int ix = 0; ix <= degree; ++ix, ++idx) + { + ref_points_flat[static_cast(idx * 3)] = + (degree > 0) ? T(ix) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 3 + 1)] = + (degree > 0) ? T(iy) / T(degree) : T(0); + ref_points_flat[static_cast(idx * 3 + 2)] = + (degree > 0) ? T(iz) / T(degree) : T(0); + } + } + } + + // Map each subcell reference point to parent reference coordinates, + // then evaluate the parent polynomial. + std::vector nodal_values(static_cast(n_dofs)); + std::vector xi_parent(static_cast(parent_tdim)); + + for (int d = 0; d < n_dofs; ++d) + { + // Map subcell reference point → parent reference. + // For simplex subcells: xi_parent = (1 - sum(xi_sub)) * v0 + xi_sub[0]*v1 + xi_sub[1]*v2 + ... + // For tensor-product subcells: bilinear/trilinear map. + std::fill(xi_parent.begin(), xi_parent.end(), T(0)); + + if (bernstein::is_simplex(subcell_type)) + { + // Barycentric coordinates: lambda_0 = 1 - sum(xi), lambda_i = xi[i-1] + T lambda0 = T(1); + for (int s = 0; s < subcell_tdim; ++s) + lambda0 -= ref_points_flat[static_cast(d * subcell_tdim + s)]; + + for (int c = 0; c < parent_tdim; ++c) + xi_parent[static_cast(c)] = + lambda0 * subcell_vertex_coords[static_cast(0 * parent_tdim + c)]; + + for (int v = 1; v < n_subcell_verts; ++v) + { + T lambda = ref_points_flat[static_cast(d * subcell_tdim + (v - 1))]; + for (int c = 0; c < parent_tdim; ++c) + xi_parent[static_cast(c)] += + lambda * subcell_vertex_coords[static_cast(v * parent_tdim + c)]; + } + } + else + { + // Tensor-product: bilinear/trilinear map. + if (subcell_tdim == 2) + { + T u = ref_points_flat[static_cast(d * 2)]; + T v = ref_points_flat[static_cast(d * 2 + 1)]; + // Bilinear: (1-u)(1-v)*v0 + u(1-v)*v1 + uv*v2 + (1-u)v*v3 + T w00 = (T(1) - u) * (T(1) - v); + T w10 = u * (T(1) - v); + T w11 = u * v; + T w01 = (T(1) - u) * v; + for (int c = 0; c < parent_tdim; ++c) + xi_parent[static_cast(c)] = + w00 * subcell_vertex_coords[static_cast(0 * parent_tdim + c)] + + w10 * subcell_vertex_coords[static_cast(1 * parent_tdim + c)] + + w11 * subcell_vertex_coords[static_cast(2 * parent_tdim + c)] + + w01 * subcell_vertex_coords[static_cast(3 * parent_tdim + c)]; + } + else if (subcell_tdim == 3) + { + T u = ref_points_flat[static_cast(d * 3)]; + T v = ref_points_flat[static_cast(d * 3 + 1)]; + T w = ref_points_flat[static_cast(d * 3 + 2)]; + T weights[8] = { + (T(1)-u)*(T(1)-v)*(T(1)-w), + u *(T(1)-v)*(T(1)-w), + u * v *(T(1)-w), + (T(1)-u)* v *(T(1)-w), + (T(1)-u)*(T(1)-v)* w, + u *(T(1)-v)* w, + u * v * w, + (T(1)-u)* v * w + }; + for (int c = 0; c < parent_tdim; ++c) + { + xi_parent[static_cast(c)] = T(0); + for (int vv = 0; vv < 8; ++vv) + xi_parent[static_cast(c)] += + weights[vv] * subcell_vertex_coords[ + static_cast(vv * parent_tdim + c)]; + } + } + } + + nodal_values[static_cast(d)] = + bernstein::evaluate(parent_cell_type, degree, parent_coeffs, + std::span(xi_parent)); + } + + // Convert sampled nodal values to Bernstein on the subcell. + bernstein::lagrange_to_bernstein( + subcell_type, degree, + std::span(ref_points_flat), + std::span(nodal_values), + subcell_coeffs); +} + +// ===================================================================== +// classify_leaf_cell +// ===================================================================== + +template +CellCertTag classify_leaf_cell(const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int cell_id, + T zero_tol, T sign_tol) +{ + // A. Check the incident-edge topology first. + { + const CellCertTag edge_topology_tag = + classify_ready_to_cut_topology(adapt_cell, level_set_id, cell_id); + if (edge_topology_tag != CellCertTag::not_classified) + return edge_topology_tag; + } + + // B. No edge roots: use exact subcell Bernstein restriction. + const int tdim = adapt_cell.tdim; + auto cell_verts = adapt_cell.entity_to_vertex[tdim][static_cast(cell_id)]; + // Get the subcell vertex coordinates in parent reference frame. + const int parent_tdim = adapt_cell.tdim; + const int n_cell_verts = static_cast(cell_verts.size()); + std::vector subcell_vertex_coords( + static_cast(n_cell_verts * parent_tdim)); + + for (int v = 0; v < n_cell_verts; ++v) + { + int gv = cell_verts[static_cast(v)]; + for (int d = 0; d < parent_tdim; ++d) + subcell_vertex_coords[static_cast(v * parent_tdim + d)] = + adapt_cell.vertex_coords[static_cast(gv * parent_tdim + d)]; + } + + cell::type subcell_type = adapt_cell.entity_types[tdim][static_cast(cell_id)]; + + std::vector subcell_coeffs; + restrict_subcell_bernstein_exact( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), + subcell_type, + std::span(subcell_vertex_coords), + subcell_coeffs); + + // Sign-hull classification. + std::span sc(subcell_coeffs); + + if (bernstein_all_zero(sc, zero_tol)) + { + // All Bernstein coefficients are zero within tolerance. + // Evaluate the level set at the cell centroid to disambiguate + // between a truly zero cell and a cell that is inside/outside + // but whose vertices all happen to lie on the zero set. + std::vector xi_centroid(static_cast(parent_tdim), T(0)); + for (int v = 0; v < n_cell_verts; ++v) + { + int gv = cell_verts[static_cast(v)]; + for (int d = 0; d < parent_tdim; ++d) + xi_centroid[static_cast(d)] += + adapt_cell.vertex_coords[static_cast(gv * parent_tdim + d)]; + } + for (int d = 0; d < parent_tdim; ++d) + xi_centroid[static_cast(d)] /= T(n_cell_verts); + + const T centroid_val = ls_cell.value( + std::span(xi_centroid.data(), + static_cast(parent_tdim))); + + if (std::fabs(centroid_val) <= zero_tol) + return CellCertTag::zero; + return (centroid_val > T(0)) ? CellCertTag::positive : CellCertTag::negative; + } + if (bernstein_all_positive(sc, sign_tol)) + return CellCertTag::positive; + if (bernstein_all_negative(sc, sign_tol)) + return CellCertTag::negative; + + // Mixed Bernstein signs — apply additional filters before declaring ambiguous. + + // B4a. MONOTONICITY FILTER (cheap, all dimensions): + // If phi has a sign-definite partial derivative in some direction on + // the subcell AND no incident edge has a root, then phi cannot have + // an interior zero-crossing. Use vertex evaluation to determine sign. + const bool no_edge_root = !cell_has_any_edge_root(adapt_cell, level_set_id, cell_id); + + if (no_edge_root + && has_monotone_direction(subcell_type, ls_cell.bernstein_order, sc, sign_tol)) + { + // Evaluate phi at the first vertex of the subcell. + std::span xi_v0( + adapt_cell.vertex_coords.data() + + static_cast(cell_verts[0]) * static_cast(parent_tdim), + static_cast(parent_tdim)); + const T val = bernstein::evaluate( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), xi_v0); + return (val >= T(0)) ? CellCertTag::positive : CellCertTag::negative; + } + + // B4b. FACE CERTIFICATION FILTER (3D only): + // For each face of the subcell, restrict the subcell Bernstein to + // the face and check if the face can have a root. If ALL faces are + // sign-definite and no edge has a root, the cell is sign-definite. + if (tdim == 3 && no_edge_root && subcell_type == cell::type::tetrahedron) + { + const int n_faces = cell::num_faces(subcell_type); + bool all_faces_sign_definite = true; + + for (int fi = 0; fi < n_faces; ++fi) + { + auto face_v = cell::tet_face(fi); + // Build face vertex coordinates in parent reference frame + // (using the subcell's local vertex ordering). + std::vector face_vertex_coords(static_cast(3 * parent_tdim)); + for (int fv = 0; fv < 3; ++fv) + { + const int local_v = face_v[static_cast(fv)]; + for (int d = 0; d < parent_tdim; ++d) + face_vertex_coords[static_cast(fv * parent_tdim + d)] = + subcell_vertex_coords[static_cast(local_v * parent_tdim + d)]; + } + + // Restrict the parent polynomial to the face. + std::vector face_coeffs; + restrict_subcell_bernstein_exact( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), + cell::type::triangle, + std::span(face_vertex_coords), + face_coeffs); + + std::span fc(face_coeffs); + if (bernstein_all_positive(fc, sign_tol) + || bernstein_all_negative(fc, sign_tol) + || bernstein_all_zero(fc, zero_tol)) + { + continue; // This face is sign-definite or zero — no root here. + } + + // Face has mixed Bernstein signs — possible root. + all_faces_sign_definite = false; + break; + } + + if (all_faces_sign_definite) + { + // No root can enter the cell through any face; evaluate at a vertex. + std::span xi_v0( + adapt_cell.vertex_coords.data() + + static_cast(cell_verts[0]) * static_cast(parent_tdim), + static_cast(parent_tdim)); + const T val = bernstein::evaluate( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), xi_v0); + return (val >= T(0)) ? CellCertTag::positive : CellCertTag::negative; + } + } + + return CellCertTag::ambiguous; +} + +// ===================================================================== +// classify_leaf_cells +// ===================================================================== + +template +void classify_leaf_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol) +{ + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + + // Ensure tag storage. + if (adapt_cell.cell_cert_tag_num_level_sets <= level_set_id) + adapt_cell.resize_cell_cert_tags(level_set_id + 1); + + for (int c = 0; c < n_cells; ++c) + { + if (adapt_cell.get_cell_cert_tag(level_set_id, c) + != CellCertTag::not_classified) + continue; + + CellCertTag tag = classify_leaf_cell( + adapt_cell, ls_cell, level_set_id, c, zero_tol, sign_tol); + adapt_cell.set_cell_cert_tag(level_set_id, c, tag); + } +} + +// ===================================================================== +// classify_leaf_face +// ===================================================================== + +template +FaceCertTag classify_leaf_face(const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int face_id, + T zero_tol, T sign_tol) +{ + // Only meaningful for 3D cells. + if (adapt_cell.tdim != 3) + return FaceCertTag::not_classified; + + // A. Check incident-edge root topology. + const bool has_root = face_has_any_edge_root(adapt_cell, level_set_id, face_id); + if (has_root) + return FaceCertTag::cut; + + // B. Restrict the parent Bernstein to the face. + auto face_verts = adapt_cell.entity_to_vertex[2][static_cast(face_id)]; + const int parent_tdim = adapt_cell.tdim; + const int n_face_verts = static_cast(face_verts.size()); + std::vector face_vertex_coords( + static_cast(n_face_verts * parent_tdim)); + + for (int v = 0; v < n_face_verts; ++v) + { + int gv = face_verts[static_cast(v)]; + for (int d = 0; d < parent_tdim; ++d) + face_vertex_coords[static_cast(v * parent_tdim + d)] = + adapt_cell.vertex_coords[static_cast(gv * parent_tdim + d)]; + } + + cell::type face_type = adapt_cell.entity_types[2][static_cast(face_id)]; + + std::vector face_coeffs; + restrict_subcell_bernstein_exact( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), + face_type, + std::span(face_vertex_coords), + face_coeffs); + + // Sign-hull classification. + std::span fc(face_coeffs); + + if (bernstein_all_zero(fc, zero_tol)) + { + // All Bernstein coefficients are zero — evaluate centroid. + std::vector xi_centroid(static_cast(parent_tdim), T(0)); + for (int v = 0; v < n_face_verts; ++v) + { + int gv = face_verts[static_cast(v)]; + for (int d = 0; d < parent_tdim; ++d) + xi_centroid[static_cast(d)] += + adapt_cell.vertex_coords[static_cast(gv * parent_tdim + d)]; + } + for (int d = 0; d < parent_tdim; ++d) + xi_centroid[static_cast(d)] /= T(n_face_verts); + + const T centroid_val = ls_cell.value( + std::span(xi_centroid.data(), + static_cast(parent_tdim))); + + if (std::fabs(centroid_val) <= zero_tol) + return FaceCertTag::zero; + return (centroid_val > T(0)) ? FaceCertTag::positive : FaceCertTag::negative; + } + if (bernstein_all_positive(fc, sign_tol)) + return FaceCertTag::positive; + if (bernstein_all_negative(fc, sign_tol)) + return FaceCertTag::negative; + + // Mixed Bernstein signs — apply monotonicity filter. + if (has_monotone_direction(face_type, ls_cell.bernstein_order, fc, sign_tol)) + { + // Evaluate phi at the first vertex of the face. + std::span xi_v0( + adapt_cell.vertex_coords.data() + + static_cast(face_verts[0]) * static_cast(parent_tdim), + static_cast(parent_tdim)); + const T val = bernstein::evaluate( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), xi_v0); + return (val >= T(0)) ? FaceCertTag::positive : FaceCertTag::negative; + } + + return FaceCertTag::ambiguous; +} + +// ===================================================================== +// classify_leaf_faces +// ===================================================================== + +template +void classify_leaf_faces(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol) +{ + if (adapt_cell.tdim != 3) + return; + + const int n_faces = adapt_cell.n_entities(2); + if (n_faces == 0) + return; + + // Ensure tag storage. + if (adapt_cell.face_cert_tag_num_level_sets <= level_set_id) + adapt_cell.resize_face_cert_tags(level_set_id + 1); + + for (int f = 0; f < n_faces; ++f) + { + if (adapt_cell.get_face_cert_tag(level_set_id, f) + != FaceCertTag::not_classified) + continue; + + FaceCertTag tag = classify_leaf_face( + adapt_cell, ls_cell, level_set_id, f, zero_tol, sign_tol); + adapt_cell.set_face_cert_tag(level_set_id, f, tag); + } +} + +// ===================================================================== +// fill_all_vertex_signs_from_level_set +// ===================================================================== + +template +void fill_all_vertex_signs_from_level_set(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol) +{ + const int n_vertices = adapt_cell.n_vertices(); + const int tdim = adapt_cell.tdim; + for (int v = 0; v < n_vertices; ++v) + { + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + if ((adapt_cell.zero_mask_per_vertex[static_cast(v)] & bit) != 0) + continue; + + std::span xi( + adapt_cell.vertex_coords.data() + + static_cast(v) * static_cast(tdim), + static_cast(tdim)); + set_vertex_sign_for_level_set( + adapt_cell, v, level_set_id, ls_cell.value(xi), zero_tol); + } +} + +// ===================================================================== +// process_ready_to_cut_cells +// ===================================================================== + +template +void process_ready_to_cut_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, + T sign_tol, + int edge_max_depth, + bool triangulate_cut_parts) +{ + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + + bool has_ready = false; + for (int c = 0; c < n_cells; ++c) + { + if (adapt_cell.get_cell_cert_tag(level_set_id, c) == CellCertTag::ready_to_cut) + { + has_ready = true; + break; + } + } + if (!has_ready) + return; + + const std::vector old_types = adapt_cell.entity_types[tdim]; + const EntityAdjacency old_cells = adapt_cell.entity_to_vertex[tdim]; + const auto edge_lookup = build_leaf_edge_lookup(adapt_cell); + + EntityAdjacency new_cells; + new_cells.offsets.push_back(0); + std::vector new_types; + std::vector old_cell_ids_for_new_cells; + std::vector source_cell_ids_for_new_cells; + std::vector refinement_reasons_for_new_cells; + std::vector explicit_current_ls_tags; + + for (int c = 0; c < n_cells; ++c) + { + auto old_cell_vertices = old_cells[static_cast(c)]; + const cell::type leaf_cell_type = old_types[static_cast(c)]; + + if (adapt_cell.get_cell_cert_tag(level_set_id, c) != CellCertTag::ready_to_cut) + { + std::vector copy(old_cell_vertices.begin(), old_cell_vertices.end()); + append_top_cell_local(new_types, new_cells, leaf_cell_type, std::span(copy)); + old_cell_ids_for_new_cells.push_back(c); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::none); + explicit_current_ls_tags.push_back(CellCertTag::not_classified); + continue; + } + + if (leaf_cell_type != cell::type::triangle + && leaf_cell_type != cell::type::tetrahedron) + { + throw std::runtime_error( + "process_ready_to_cut_cells: ready_to_cut only implemented for triangle and tetrahedron leaves"); + } + + std::vector vertex_coords( + static_cast(old_cell_vertices.size() * tdim), T(0)); + for (std::size_t j = 0; j < old_cell_vertices.size(); ++j) + { + const int gv = old_cell_vertices[j]; + for (int d = 0; d < tdim; ++d) + { + vertex_coords[static_cast(j * tdim + d)] = + adapt_cell.vertex_coords[static_cast(gv * tdim + d)]; + } + } + const std::vector ls_values = + gather_leaf_cell_vertex_level_set_values(adapt_cell, ls_cell, c); + + const std::uint64_t current_level_set_bit = std::uint64_t(1) << level_set_id; + std::vector current_level_set_zero(old_cell_vertices.size(), false); + bool has_strict_negative = false; + bool has_strict_positive = false; + bool has_zero = false; + for (std::size_t j = 0; j < ls_values.size(); ++j) + { + const T value = ls_values[j]; + const int gv = old_cell_vertices[j]; + current_level_set_zero[j] = + (adapt_cell.zero_mask_per_vertex[static_cast(gv)] + & current_level_set_bit) != 0; + has_strict_negative = has_strict_negative || value < -zero_tol; + has_strict_positive = has_strict_positive || value > zero_tol; + has_zero = has_zero || current_level_set_zero[j]; + } + if (!(has_strict_negative && has_strict_positive)) + { + std::vector copy(old_cell_vertices.begin(), old_cell_vertices.end()); + append_top_cell_local(new_types, new_cells, leaf_cell_type, std::span(copy)); + old_cell_ids_for_new_cells.push_back(c); + CellCertTag copied_tag = CellCertTag::zero; + if (has_strict_negative) + copied_tag = CellCertTag::negative; + else if (has_strict_positive) + copied_tag = CellCertTag::positive; + else if (!has_zero) + copied_tag = CellCertTag::not_classified; + explicit_current_ls_tags.push_back(copied_tag); + continue; + } + + std::array old_edge_ids_by_local_edge; + old_edge_ids_by_local_edge.fill(-1); + const auto ledges = cell::edges(leaf_cell_type); + for (std::size_t le = 0; le < ledges.size(); ++le) + { + const int a = old_cell_vertices[static_cast(ledges[le][0])]; + const int b = old_cell_vertices[static_cast(ledges[le][1])]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto edge_it = edge_lookup.find(key); + if (edge_it == edge_lookup.end()) + { + throw std::runtime_error( + "process_ready_to_cut_cells: missing leaf edge for ready-to-cut cell " + + std::to_string(c) + " type=" + + cell_type_to_str(leaf_cell_type) + " local_edge=" + + std::to_string(le) + " key=(" + + std::to_string(key.first) + "," + + std::to_string(key.second) + ")"); + } + old_edge_ids_by_local_edge[le] = edge_it->second; + } + + std::map token_to_vertex; + for (std::size_t j = 0; j < old_cell_vertices.size(); ++j) + token_to_vertex[100 + static_cast(j)] = old_cell_vertices[j]; + for (std::size_t le = 0; le < ledges.size(); ++le) + { + const int old_edge_id = old_edge_ids_by_local_edge[le]; + if (old_edge_id < 0) + continue; + if (adapt_cell.get_edge_root_tag(level_set_id, old_edge_id) + != EdgeRootTag::one_root) + { + continue; + } + + const int root_vertex_id = ensure_one_root_vertex_on_edge( + adapt_cell, ls_cell, level_set_id, old_edge_id, + zero_tol, sign_tol, edge_max_depth); + if (root_vertex_id >= 0) + token_to_vertex[static_cast(le)] = root_vertex_id; + } + + if (leaf_cell_type == cell::type::triangle) + { + if (has_zero) + { + auto append_zero_vertex_triangle_side = + [&](bool negative_side, CellCertTag side_tag) + { + std::vector tokens; + auto append_token = [&](int token) + { + if (tokens.empty() || tokens.back() != token) + tokens.push_back(token); + }; + + for (int lv = 0; lv < 3; ++lv) + { + const int next = (lv + 1) % 3; + const T vi = ls_values[static_cast(lv)]; + const T vj = ls_values[static_cast(next)]; + const bool zero_i = + current_level_set_zero[static_cast(lv)]; + const bool inside_i = negative_side + ? (zero_i || vi < -zero_tol) + : (zero_i || vi > zero_tol); + if (inside_i) + append_token(100 + lv); + + const bool strict_cross = + (vi < -zero_tol && vj > zero_tol) + || (vi > zero_tol && vj < -zero_tol); + if (strict_cross) + { + const int local_edge = + basix_edge_id_for_vertices(leaf_cell_type, lv, next); + append_token(local_edge); + } + } + + if (tokens.size() > 1 && tokens.front() == tokens.back()) + tokens.pop_back(); + if (tokens.size() < 3) + return; + if (tokens.size() != 3) + { + throw std::runtime_error( + "process_ready_to_cut_cells: zero-vertex triangle clipping " + "expected a triangle"); + } + + std::vector mapped(tokens.size(), -1); + for (std::size_t j = 0; j < tokens.size(); ++j) + { + auto token_it = token_to_vertex.find(tokens[j]); + if (token_it == token_to_vertex.end()) + { + throw std::runtime_error( + "process_ready_to_cut_cells: missing zero-vertex " + "triangle token " + std::to_string(tokens[j])); + } + mapped[j] = token_it->second; + } + + append_top_cell_local( + new_types, new_cells, cell::type::triangle, + std::span(mapped)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back( + CellRefinementReason::cut_level_set); + explicit_current_ls_tags.push_back(side_tag); + }; + + append_zero_vertex_triangle_side(true, CellCertTag::negative); + append_zero_vertex_triangle_side(false, CellCertTag::positive); + continue; + } + + cell::CutCell negative_part; + cell::CutCell positive_part; + cell::triangle::cut( + std::span(vertex_coords), + tdim, + std::span(ls_values), + "phi<0", + negative_part, + triangulate_cut_parts); + cell::triangle::cut( + std::span(vertex_coords), + tdim, + std::span(ls_values), + "phi>0", + positive_part, + triangulate_cut_parts); + + append_ready_cut_part_cells( + adapt_cell, ls_cell, level_set_id, c, negative_part, leaf_cell_type, + old_cell_vertices, std::span(old_edge_ids_by_local_edge.data(), 3), + new_types, new_cells, old_cell_ids_for_new_cells, + source_cell_ids_for_new_cells, refinement_reasons_for_new_cells, + explicit_current_ls_tags, + token_to_vertex, zero_tol, CellCertTag::negative); + append_ready_cut_part_cells( + adapt_cell, ls_cell, level_set_id, c, positive_part, leaf_cell_type, + old_cell_vertices, std::span(old_edge_ids_by_local_edge.data(), 3), + new_types, new_cells, old_cell_ids_for_new_cells, + source_cell_ids_for_new_cells, refinement_reasons_for_new_cells, + explicit_current_ls_tags, + token_to_vertex, zero_tol, CellCertTag::positive); + } + else + { + if (has_zero) + { + auto append_zero_vertex_tetra_side = + [&](bool negative_side, CellCertTag side_tag) + { + std::vector zero_vertices; + std::vector inside_vertices; + std::vector outside_vertices; + + for (int lv = 0; lv < 4; ++lv) + { + const T value = ls_values[static_cast(lv)]; + if (current_level_set_zero[static_cast(lv)]) + { + zero_vertices.push_back(lv); + continue; + } + + const bool inside = + negative_side ? (value < -zero_tol) : (value > zero_tol); + if (inside) + inside_vertices.push_back(lv); + else + outside_vertices.push_back(lv); + } + + auto edge_token = [&](int a, int b) + { + const int token = + basix_edge_id_for_vertices(leaf_cell_type, a, b); + if (token < 0) + { + throw std::runtime_error( + "process_ready_to_cut_cells: missing tetra edge token"); + } + return token; + }; + + auto append_tet_tokens = [&](const std::array& tokens) + { + std::array mapped; + for (std::size_t j = 0; j < tokens.size(); ++j) + { + auto token_it = token_to_vertex.find(tokens[j]); + if (token_it == token_to_vertex.end()) + { + throw std::runtime_error( + "process_ready_to_cut_cells: missing zero-vertex " + "tetra token " + std::to_string(tokens[j])); + } + mapped[j] = token_it->second; + } + + std::set unique(mapped.begin(), mapped.end()); + if (unique.size() != mapped.size()) + return; + + append_top_cell_local( + new_types, new_cells, cell::type::tetrahedron, + std::span(mapped)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back( + CellRefinementReason::cut_level_set); + explicit_current_ls_tags.push_back(side_tag); + }; + + if (zero_vertices.size() == 1) + { + const int z = zero_vertices[0]; + if (inside_vertices.size() == 1 + && outside_vertices.size() == 2) + { + const int a = inside_vertices[0]; + append_tet_tokens({ + 100 + z, + 100 + a, + edge_token(a, outside_vertices[0]), + edge_token(a, outside_vertices[1]), + }); + return; + } + + if (inside_vertices.size() == 2 + && outside_vertices.size() == 1) + { + const int a = inside_vertices[0]; + const int b = inside_vertices[1]; + const int o = outside_vertices[0]; + const int ra = edge_token(a, o); + const int rb = edge_token(b, o); + append_tet_tokens({100 + z, 100 + a, 100 + b, rb}); + append_tet_tokens({100 + z, 100 + a, rb, ra}); + return; + } + } + + if (zero_vertices.size() == 2 + && inside_vertices.size() == 1 + && outside_vertices.size() == 1) + { + append_tet_tokens({ + 100 + zero_vertices[0], + 100 + zero_vertices[1], + 100 + inside_vertices[0], + edge_token(inside_vertices[0], outside_vertices[0]), + }); + return; + } + + throw std::runtime_error( + "process_ready_to_cut_cells: unsupported zero-vertex " + "tetrahedron clipping case"); + }; + + append_zero_vertex_tetra_side(true, CellCertTag::negative); + append_zero_vertex_tetra_side(false, CellCertTag::positive); + continue; + } + + cell::CutCell negative_part; + cell::CutCell positive_part; + cell::tetrahedron::cut( + std::span(vertex_coords), tdim, + std::span(ls_values), "phi<0", + negative_part, triangulate_cut_parts); + cell::tetrahedron::cut( + std::span(vertex_coords), tdim, + std::span(ls_values), "phi>0", + positive_part, triangulate_cut_parts); + + append_ready_cut_part_cells( + adapt_cell, ls_cell, level_set_id, c, negative_part, leaf_cell_type, + old_cell_vertices, std::span(old_edge_ids_by_local_edge.data(), 6), + new_types, new_cells, old_cell_ids_for_new_cells, + source_cell_ids_for_new_cells, refinement_reasons_for_new_cells, + explicit_current_ls_tags, + token_to_vertex, zero_tol, CellCertTag::negative); + append_ready_cut_part_cells( + adapt_cell, ls_cell, level_set_id, c, positive_part, leaf_cell_type, + old_cell_vertices, std::span(old_edge_ids_by_local_edge.data(), 6), + new_types, new_cells, old_cell_ids_for_new_cells, + source_cell_ids_for_new_cells, refinement_reasons_for_new_cells, + explicit_current_ls_tags, + token_to_vertex, zero_tol, CellCertTag::positive); + } + } + + apply_topology_update_preserve_certification( + adapt_cell, std::move(new_types), std::move(new_cells), + std::span(old_cell_ids_for_new_cells), + std::span(source_cell_ids_for_new_cells), + std::span(refinement_reasons_for_new_cells)); + + for (int c = 0; c < adapt_cell.n_entities(tdim); ++c) + { + if (c < static_cast(refinement_reasons_for_new_cells.size()) + && refinement_reasons_for_new_cells[static_cast(c)] + == CellRefinementReason::cut_level_set + && c < static_cast(adapt_cell.entity_source_level_set[tdim].size())) + { + adapt_cell.entity_source_level_set[tdim][static_cast(c)] = + static_cast(level_set_id); + } + } + + const int new_num_cells = adapt_cell.n_entities(tdim); + for (int c = 0; c < new_num_cells; ++c) + { + if (explicit_current_ls_tags[static_cast(c)] == CellCertTag::not_classified) + continue; + adapt_cell.set_cell_cert_tag( + level_set_id, c, explicit_current_ls_tags[static_cast(c)]); + } + +} + +template +bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int cell_id) +{ + const int tdim = adapt_cell.tdim; + if (cell_id < 0 || cell_id >= adapt_cell.n_entities(tdim)) + return false; + + const auto edge_lookup = build_leaf_edge_lookup(adapt_cell); + auto verts = adapt_cell.entity_to_vertex[tdim][static_cast(cell_id)]; + const cell::type ctype = adapt_cell.entity_types[tdim][static_cast(cell_id)]; + const auto ledges = cell::edges(ctype); + + int selected_edge = -1; + T selected_value = -std::numeric_limits::infinity(); + for (const auto& le : ledges) + { + const int a = verts[static_cast(le[0])]; + const int b = verts[static_cast(le[1])]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto it = edge_lookup.find(key); + if (it == edge_lookup.end()) + continue; + + std::vector midpoint(static_cast(tdim), T(0)); + for (int d = 0; d < tdim; ++d) + { + midpoint[static_cast(d)] = + T(0.5) + * (adapt_cell.vertex_coords[static_cast(a * tdim + d)] + + adapt_cell.vertex_coords[static_cast(b * tdim + d)]); + } + const T value = ls_cell.value( + std::span(midpoint.data(), midpoint.size())); + if (selected_edge < 0 || value > selected_value) + { + selected_edge = it->second; + selected_value = value; + } + } + + if (selected_edge < 0) + return false; + + const int n_edges = adapt_cell.n_entities(1); + if (adapt_cell.edge_root_tag_num_level_sets <= level_set_id) + adapt_cell.resize_edge_root_tags(level_set_id + 1); + if (adapt_cell.edge_green_split_has_value.size() + < static_cast((level_set_id + 1) * n_edges)) + { + adapt_cell.resize_green_split_data(level_set_id + 1); + } + + const auto idx = + static_cast(level_set_id * n_edges + selected_edge); + adapt_cell.set_edge_root_tag( + level_set_id, selected_edge, EdgeRootTag::multiple_roots); + adapt_cell.edge_green_split_param[idx] = T(0.5); + adapt_cell.edge_green_split_has_value[idx] = 1; + + return refine_green_on_multiple_root_edges(adapt_cell, level_set_id); +} + +// ===================================================================== +// certify_and_refine +// ===================================================================== + +template +void certify_and_refine(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int max_iterations, + T zero_tol, T sign_tol, + int edge_max_depth) +{ + for (int iter = 0; iter < max_iterations; ++iter) + { + // 1. Classify edges. + classify_new_edges(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol, edge_max_depth); + + // 1b. Build faces and classify them (3D only). + if (adapt_cell.tdim == 3) + { + build_faces(adapt_cell); + classify_leaf_faces(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol); + } + + // 2. Classify cells. + classify_leaf_cells(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol); + + // 3. Green refinement: multiple_roots edges. + bool did_green = false; + const int n_edges = adapt_cell.n_entities(1); + for (int e = 0; e < n_edges; ++e) + { + if (adapt_cell.get_edge_root_tag(level_set_id, e) + == EdgeRootTag::multiple_roots) + { + did_green = true; + break; + } + } + + if (did_green) + { + if (refine_green_on_multiple_root_edges(adapt_cell, level_set_id)) + { + fill_all_vertex_signs_from_level_set( + adapt_cell, ls_cell, level_set_id, zero_tol); + continue; + } + } + + // 4. Red refinement: ambiguous cells. + bool did_red = false; + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + for (int c = 0; c < n_cells; ++c) + { + if (adapt_cell.get_cell_cert_tag(level_set_id, c) + == CellCertTag::ambiguous) + { + did_red = true; + break; + } + } + + if (did_red) + { + if (refine_red_on_ambiguous_cells(adapt_cell, level_set_id)) + { + fill_all_vertex_signs_from_level_set( + adapt_cell, ls_cell, level_set_id, zero_tol); + continue; + } + } + + // 5. No refinement needed — stop. + break; + } +} + +template +void certify_refine_and_process_ready_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int max_iterations, + T zero_tol, T sign_tol, + int edge_max_depth, + bool triangulate_cut_parts) +{ + fill_all_vertex_signs_from_level_set(adapt_cell, ls_cell, level_set_id, zero_tol); + certify_and_refine(adapt_cell, ls_cell, level_set_id, + max_iterations, zero_tol, sign_tol, edge_max_depth); + fill_all_vertex_signs_from_level_set(adapt_cell, ls_cell, level_set_id, zero_tol); + process_ready_to_cut_cells(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol, edge_max_depth, + triangulate_cut_parts); + fill_all_vertex_signs_from_level_set(adapt_cell, ls_cell, level_set_id, zero_tol); + build_edges(adapt_cell); + if (adapt_cell.tdim == 3) + build_faces(adapt_cell); + recompute_active_level_set_masks(adapt_cell, level_set_id + 1); + rebuild_zero_entity_inventory(adapt_cell); +} + +// ===================================================================== +// Explicit template instantiations +// ===================================================================== + +template void restrict_subcell_bernstein_exact(cell::type, int, + std::span, + cell::type, + std::span, + std::vector&); +template void restrict_subcell_bernstein_exact(cell::type, int, + std::span, + cell::type, + std::span, + std::vector&); + +template CellCertTag classify_leaf_cell(const AdaptCell&, + const LevelSetCell&, + int, int, double, double); +template CellCertTag classify_leaf_cell(const AdaptCell&, + const LevelSetCell&, + int, int, float, float); +template CellCertTag classify_leaf_cell(const AdaptCell&, + const LevelSetCell&, + int, int, double, double); + +template void classify_leaf_cells(AdaptCell&, + const LevelSetCell&, + int, double, double); +template void classify_leaf_cells(AdaptCell&, + const LevelSetCell&, + int, float, float); +template void classify_leaf_cells(AdaptCell&, + const LevelSetCell&, + int, double, double); + +template FaceCertTag classify_leaf_face(const AdaptCell&, + const LevelSetCell&, + int, int, double, double); +template FaceCertTag classify_leaf_face(const AdaptCell&, + const LevelSetCell&, + int, int, float, float); +template FaceCertTag classify_leaf_face(const AdaptCell&, + const LevelSetCell&, + int, int, double, double); + +template void classify_leaf_faces(AdaptCell&, + const LevelSetCell&, + int, double, double); +template void classify_leaf_faces(AdaptCell&, + const LevelSetCell&, + int, float, float); +template void classify_leaf_faces(AdaptCell&, + const LevelSetCell&, + int, double, double); + +template void fill_all_vertex_signs_from_level_set(AdaptCell&, + const LevelSetCell&, + int, double); +template void fill_all_vertex_signs_from_level_set(AdaptCell&, + const LevelSetCell&, + int, float); +template void fill_all_vertex_signs_from_level_set(AdaptCell&, + const LevelSetCell&, + int, double); +template void fill_all_vertex_signs_from_level_set(AdaptCell&, + const LevelSetCell&, + int, float); + +template void process_ready_to_cut_cells(AdaptCell&, + const LevelSetCell&, + int, double, double, int, bool); +template void process_ready_to_cut_cells(AdaptCell&, + const LevelSetCell&, + int, float, float, int, bool); +template void process_ready_to_cut_cells(AdaptCell&, + const LevelSetCell&, + int, double, double, int, bool); +template void process_ready_to_cut_cells(AdaptCell&, + const LevelSetCell&, + int, float, float, int, bool); + +template bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell&, + const LevelSetCell&, + int, + int); +template bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell&, + const LevelSetCell&, + int, + int); +template bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell&, + const LevelSetCell&, + int, + int); +template bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell&, + const LevelSetCell&, + int, + int); + +template void certify_and_refine(AdaptCell&, + const LevelSetCell&, + int, int, double, double, int); +template void certify_and_refine(AdaptCell&, + const LevelSetCell&, + int, int, float, float, int); +template void certify_and_refine(AdaptCell&, + const LevelSetCell&, + int, int, double, double, int); + +template void certify_refine_and_process_ready_cells(AdaptCell&, + const LevelSetCell&, + int, int, double, double, int, bool); +template void certify_refine_and_process_ready_cells(AdaptCell&, + const LevelSetCell&, + int, int, float, float, int, bool); +template void certify_refine_and_process_ready_cells(AdaptCell&, + const LevelSetCell&, + int, int, double, double, int, bool); +template void certify_refine_and_process_ready_cells(AdaptCell&, + const LevelSetCell&, + int, int, float, float, int, bool); + +} // namespace cutcells diff --git a/cpp/src/cell_certification.h b/cpp/src/cell_certification.h new file mode 100644 index 0000000..c2f3799 --- /dev/null +++ b/cpp/src/cell_certification.h @@ -0,0 +1,207 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +#include "adapt_cell.h" +#include "cell_types.h" +#include "level_set_cell.h" + +namespace cutcells +{ + +// ===================================================================== +// Exact subcell Bernstein restriction +// ===================================================================== + +/// Compute the Bernstein coefficients of a parent polynomial restricted +/// to a leaf subcell. +/// +/// The subcell is defined by its vertex reference coordinates (in the +/// parent reference frame). The restriction is exact: sample at +/// (degree+1)^tdim control points, then invert the Bernstein matrix. +/// +/// @param parent_cell_type Type of the parent cell. +/// @param degree Polynomial degree. +/// @param parent_coeffs Bernstein coefficients on the parent cell. +/// @param subcell_type Type of the subcell (triangle, tet, quad, ...). +/// @param subcell_vertex_coords Reference coordinates of subcell vertices (flat: nverts * tdim). +/// @param[out] subcell_coeffs Bernstein coefficients on the subcell. +template +void restrict_subcell_bernstein_exact(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + cell::type subcell_type, + std::span subcell_vertex_coords, + std::vector& subcell_coeffs); + +// ===================================================================== +// Cell classifier +// ===================================================================== + +/// Classify a single leaf cell for one level set. +/// +/// Logic: +/// A. If the incident edge pattern is a directly cuttable simplex case: +/// - triangle with exactly 2 one_root edges and no multiple_roots +/// - tetrahedron with exactly 3 or 4 one_root edges and no multiple_roots +/// → ready_to_cut. +/// B. Else if any incident edge has tag one_root or multiple_roots → cut. +/// Zero edges alone fall through so later level sets can still classify +/// or refine cells whose interface is inherited from an earlier cut. +/// C. Otherwise, restrict the parent Bernstein to the subcell and check +/// the sign hull: +/// - all positive → positive +/// - all negative → negative +/// - all zero → zero +/// - mixed → ambiguous +/// +/// @param adapt_cell The AdaptCell. +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set. +/// @param cell_id Index of the leaf cell in entity_to_vertex[tdim]. +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +/// @return CellCertTag. +template +CellCertTag classify_leaf_cell(const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int cell_id, + T zero_tol, T sign_tol); + +/// Classify all not-yet-classified leaf cells for one level set. +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set. +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +template +void classify_leaf_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol); + +// ===================================================================== +// Face (facet) classifier (3D only) +// ===================================================================== + +/// Classify a single leaf face for one level set. +/// +/// Logic mirrors classify_leaf_cell but operates on a 2D face entity +/// (dimension 2) of a 3D AdaptCell: +/// 1. Check incident-edge root topology for a valid 2D cut pattern. +/// 2. Restrict the parent Bernstein to the face and test the sign hull. +/// 3. Apply monotonicity filter. +/// 4. If the centroid is needed (all-zero case), evaluate there. +/// 5. Otherwise → ambiguous. +/// +/// @param adapt_cell The AdaptCell. +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set. +/// @param face_id Index of the leaf face in entity_to_vertex[2]. +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +/// @return FaceCertTag. +template +FaceCertTag classify_leaf_face(const AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int face_id, + T zero_tol, T sign_tol); + +/// Classify all not-yet-classified leaf faces for one level set. +/// +/// Face entities (entity_to_vertex[2]) must already be populated via +/// build_faces(). Only operates when tdim == 3. +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set. +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +template +void classify_leaf_faces(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol); + +/// Evaluate the level set on every current AdaptCell vertex and update the +/// sign/zero masks for that level set id. +template +void fill_all_vertex_signs_from_level_set(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol); + +/// Replace leaf cells marked ready_to_cut by the LUT cut decomposition on the +/// positive and negative side. +template +void process_ready_to_cut_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, + T sign_tol, + int edge_max_depth, + bool triangulate_cut_parts = false); + +/// Green-refine a ready leaf cell through the midpoint of the cell edge whose +/// midpoint has the largest level-set value. +template +bool refine_ready_cell_on_largest_midpoint_value( + AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int cell_id); + +// ===================================================================== +// Top-level certification + refinement driver +// ===================================================================== + +/// Iterative certification and refinement loop. +/// +/// Per iteration: +/// 1. classify_new_edges(...) +/// 2. classify_leaf_cells(...) +/// 3. If any multiple_roots edges → green refine (priority) +/// 4. Else if any ambiguous cells → red refine +/// 5. Stop if no refinement occurred. +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set. +/// @param max_iterations Maximum number of refine-reclassify iterations. +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +/// @param edge_max_depth Maximum subdivision depth for edge topology classification. +template +void certify_and_refine(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int max_iterations, + T zero_tol, T sign_tol, + int edge_max_depth); + +/// Full local single-level-set pipeline: +/// 1. stamp vertex signs +/// 2. certify + green/red refine until stable +/// 3. replace ready_to_cut cells by the LUT decomposition +/// 4. restamp vertex signs on the final leaf mesh +template +void certify_refine_and_process_ready_cells(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + int max_iterations, + T zero_tol, T sign_tol, + int edge_max_depth, + bool triangulate_cut_parts = false); + +} // namespace cutcells diff --git a/cpp/src/cell_flags.h b/cpp/src/cell_flags.h index 1e880e8..c2ae69d 100644 --- a/cpp/src/cell_flags.h +++ b/cpp/src/cell_flags.h @@ -30,7 +30,7 @@ namespace cutcells phigt0 = 2 }; - inline cut_type string_to_cut_type(std::string type_str) + inline cut_type string_to_cut_type(const std::string& type_str) { if(type_str=="phi<0") return cut_type::philt0; diff --git a/cpp/src/cell_subdivision.h b/cpp/src/cell_subdivision.h index 2bc5056..d7d57eb 100644 --- a/cpp/src/cell_subdivision.h +++ b/cpp/src/cell_subdivision.h @@ -3,32 +3,64 @@ // This file is part of CutFEMx // // SPDX-License-Identifier: MIT +#pragma once + #include //These tables describe the subdivision of cells by inserting one node at each edge //of the cell namespace cutcells::cell { - std::array, 2> interval_subdivision_table{ - {{0,2}, - {2,1}} - }; + inline constexpr std::array, 2> interval_subdivision_table = {{ + {0, 2}, + {2, 1} + }}; - std::array, 4> triangle_subdivision_table{ - {{0,5,4}, - {5,1,3}, - {4,3,2}, - {5,3,4}} - }; + inline constexpr std::array, 4> triangle_subdivision_table = {{ + {0, 5, 4}, + {5, 1, 3}, + {4, 3, 2}, + {5, 3, 4} + }}; //Notation: 0,1,2,3,e01(9),e02(8),e03(7),e12(6),e13(5),e23(4) //where exy denotes middle point on edge between x and y //Node ordering is chosen according to basix (fenicsx) - std::array, 8> tetrahedron_subdivision_table{ - {// four tetrahedra from each vertex - {0,9,8,7},{1,9,5,6},{2,6,4,8},{3,7,4,5},{9,6,8,7},{9,5,7,8},{6,8,7,4},{6,7,5,4} - } - }; + inline constexpr std::array, 8> tetrahedron_subdivision_table = {{ + {0, 9, 8, 7}, {1, 9, 5, 6}, {2, 6, 4, 8}, {3, 7, 4, 5}, + {9, 6, 8, 7}, {9, 5, 6, 7}, {6, 8, 7, 4}, {6, 7, 5, 4} + }}; + +// Quadrilateral P2 node numbering in Basix ordering: +// 0,1,2,3 (vertices) +// e01=4, e02=5, e13=6, e23=7 (edge midpoints; Basix edge order) +// f0123=8 (cell center) +// +// Subdivision into 4 quads (each keeps hexa/quad vertex layout used in this codebase). + inline constexpr std::array, 4> quadrilateral_subdivision_table = {{ + {0, 4, 5, 8}, + {4, 1, 8, 6}, + {5, 8, 2, 7}, + {8, 6, 7, 3} + }}; + +// Hexahedron P2 node numbering in Basix ordering: +// Vertices: 0..7 +// Edge midpoints (Basix edge order in cell_topology.h): 8..19 +// Face centers (Basix face order): 20..25 +// Cell center: 26 +// +// 8-way octree-like subdivision into hexahedra. + inline constexpr std::array, 8> hexahedron_subdivision_table = {{ + {0, 8, 9, 20, 10, 21, 22, 26}, + {8, 1, 20, 11, 21, 12, 26, 23}, + {9, 20, 2, 13, 22, 26, 14, 24}, + {20, 11, 13, 3, 26, 23, 24, 15}, + {10, 21, 22, 26, 4, 16, 17, 25}, + {21, 12, 26, 23, 16, 5, 25, 18}, + {22, 26, 14, 24, 17, 25, 6, 19}, + {26, 23, 24, 15, 25, 18, 19, 7} + }}; // basix -> vtk map 0, 1, 2, 3, 9, 8, 5, 7, 6, 4 // basix: e01: 9, e12: 6, e02: 8, e03: 7, e13: 5, e23: 4 diff --git a/cpp/src/cell_topology.h b/cpp/src/cell_topology.h index 266cbff..4dec37e 100644 --- a/cpp/src/cell_topology.h +++ b/cpp/src/cell_topology.h @@ -25,95 +25,105 @@ inline constexpr std::array, 1> interval_edges = {{ }}; /// Triangle: 3 edges -/// VTK edge order: (0,1), (1,2), (2,0) +/// Basix vertex order: 0=(0,0), 1=(1,0), 2=(0,1) +/// Basix edge order: 0=(1,2), 1=(0,2), 2=(0,1) inline constexpr std::array, 3> triangle_edges = {{ - {0, 1}, {1, 2}, {2, 0} + {1, 2}, {0, 2}, {0, 1} }}; /// Quadrilateral: 4 edges -/// VTK edge order: (0,1), (1,2), (2,3), (3,0) +/// Basix vertex order: 0=(0,0), 1=(1,0), 2=(0,1), 3=(1,1) +/// Basix edge order: 0=(0,1), 1=(0,2), 2=(1,3), 3=(2,3) /// -/// 3 --- 2 +/// 2 --- 3 /// | | /// 0 --- 1 inline constexpr std::array, 4> quadrilateral_edges = {{ - {0, 1}, {1, 2}, {2, 3}, {3, 0} + {0, 1}, {0, 2}, {1, 3}, {2, 3} }}; +/// Tetrahedron: 4 triangular faces +/// Basix face order: opposite vertex 0, 1, 2, 3 +inline constexpr std::array, 4> tetrahedron_faces = {{ + {1, 2, 3}, // face 0: opposite vertex 0 + {0, 2, 3}, // face 1: opposite vertex 1 + {0, 1, 3}, // face 2: opposite vertex 2 + {0, 1, 2} // face 3: opposite vertex 3 +}}; +inline constexpr std::array tetrahedron_face_sizes = {3, 3, 3, 3}; + /// Tetrahedron: 6 edges -/// VTK edge order: (0,1), (1,2), (2,0), (0,3), (1,3), (2,3) +/// Basix vertex order: 0=(0,0,0), 1=(1,0,0), 2=(0,1,0), 3=(0,0,1) +/// Basix edge order: 0=(2,3), 1=(1,3), 2=(1,2), 3=(0,3), 4=(0,2), 5=(0,1) inline constexpr std::array, 6> tetrahedron_edges = {{ - {0, 1}, {1, 2}, {2, 0}, {0, 3}, {1, 3}, {2, 3} + {2, 3}, {1, 3}, {1, 2}, {0, 3}, {0, 2}, {0, 1} }}; /// Hexahedron: 12 edges -/// VTK vertex numbering: -/// 7 -------- 6 +/// Basix vertex numbering: +/// 0=(0,0,0), 1=(1,0,0), 2=(0,1,0), 3=(1,1,0), +/// 4=(0,0,1), 5=(1,0,1), 6=(0,1,1), 7=(1,1,1) +/// +/// 6 -------- 7 /// /| /| /// / | / | /// 4 -------- 5 | -/// | 3 ------|-- 2 +/// | 2 ------|-- 3 /// | / | / /// |/ |/ /// 0 -------- 1 /// -/// VTK edge order: -/// Base: (0,1), (1,2), (2,3), (3,0) -/// Top: (4,5), (5,6), (6,7), (7,4) -/// Verticals: (0,4), (1,5), (2,6), (3,7) +/// Basix edge order: +/// Bottom face: (0,1),(0,2),(1,3),(2,3) +/// Verticals: (0,4),(1,5),(2,6),(3,7) +/// Top face: (4,5),(4,6),(5,7),(6,7) inline constexpr std::array, 12> hexahedron_edges = {{ - // Base edges (0-3) - {0, 1}, {1, 2}, {2, 3}, {3, 0}, - // Top edges (4-7) - {4, 5}, {5, 6}, {6, 7}, {7, 4}, - // Vertical edges (8-11) - {0, 4}, {1, 5}, {2, 6}, {3, 7} + // Bottom face edges (0-3) + {0, 1}, {0, 2}, {1, 3}, {2, 3}, + // Vertical edges (4-7) + {0, 4}, {1, 5}, {2, 6}, {3, 7}, + // Top face edges (8-11) + {4, 5}, {4, 6}, {5, 7}, {6, 7} }}; /// Prism (Wedge): 9 edges -/// VTK vertex numbering: -/// 2 -/// /|\ -/// / | \ -/// 0-----1 -/// | | | -/// | 5 | -/// | /|\ | -/// |/ | \| -/// 3-----4 +/// Basix vertex numbering (same as VTK for prism): +/// 0=(0,0,0), 1=(1,0,0), 2=(0,1,0), 3=(0,0,1), 4=(1,0,1), 5=(0,1,1) /// -/// VTK edge order: -/// Bottom triangle: (0,1), (1,2), (2,0) -/// Top triangle: (3,4), (4,5), (5,3) -/// Verticals: (0,3), (1,4), (2,5) +/// Basix edge order: +/// Bottom triangle: (0,1),(0,2),(1,2) +/// Verticals: (0,3),(1,4),(2,5) +/// Top triangle: (3,4),(3,5),(4,5) inline constexpr std::array, 9> prism_edges = {{ // Bottom triangle (0-2) - {0, 1}, {1, 2}, {2, 0}, - // Top triangle (3-5) - {3, 4}, {4, 5}, {5, 3}, - // Vertical edges (6-8) - {0, 3}, {1, 4}, {2, 5} + {0, 1}, {0, 2}, {1, 2}, + // Vertical edges (3-5) + {0, 3}, {1, 4}, {2, 5}, + // Top triangle (6-8) + {3, 4}, {3, 5}, {4, 5} }}; /// Pyramid: 8 edges -/// VTK vertex numbering: +/// Basix vertex numbering: +/// 0=(0,0,0), 1=(1,0,0), 2=(0,1,0), 3=(1,1,0), 4=(0,0,1) (apex) +/// /// 4 /// /|\ /// / | \ /// / | \ /// / | \ -/// 3----|----2 +/// 2----|----3 /// | | | /// 0---------1 /// -/// VTK edge order: -/// Base: (0,1), (1,2), (2,3), (3,0) -/// Apex connections: (0,4), (1,4), (2,4), (3,4) +/// Basix edge order: +/// Base: (0,1),(0,2),(1,3),(2,3) +/// Apex connections: (0,4),(1,4),(3,4),(2,4) inline constexpr std::array, 8> pyramid_edges = {{ // Base edges (0-3) - {0, 1}, {1, 2}, {2, 3}, {3, 0}, + {0, 1}, {0, 2}, {1, 3}, {2, 3}, // Apex edges (4-7) - {0, 4}, {1, 4}, {2, 4}, {3, 4} + {0, 4}, {1, 4}, {3, 4}, {2, 4} }}; //----------------------------------------------------------------------------- @@ -173,28 +183,35 @@ inline std::span> edges(type cell_type) /// Hexahedron: 6 faces (quads) /// Face order: -X, +X, -Y, +Y, -Z, +Z (left, right, front, back, bottom, top) +/// Vertex ordering follows basix tensor-product convention for quadrilateral: +/// basix quad: 0=(0,0), 1=(1,0), 2=(0,1), 3=(1,1) (NOT cyclic). +/// Conversion from VTK cyclic {a,b,c,d} -> basix {a,b,d,c}. inline constexpr std::array, 6> hexahedron_faces = {{ - {0, 3, 7, 4}, // -X (left) - {1, 2, 6, 5}, // +X (right) - {0, 1, 5, 4}, // -Y (front) - {3, 2, 6, 7}, // +Y (back) - {0, 1, 2, 3}, // -Z (bottom) - {4, 5, 6, 7} // +Z (top) + {0, 3, 4, 7}, // -X (left) was VTK {0,3,7,4} + {1, 2, 5, 6}, // +X (right) was VTK {1,2,6,5} + {0, 1, 4, 5}, // -Y (front) was VTK {0,1,5,4} + {3, 2, 7, 6}, // +Y (back) was VTK {3,2,6,7} + {0, 1, 2, 3}, // -Z (bottom) was VTK {0,1,3,2} -> basix {0,1,2,3} + {4, 5, 6, 7} // +Z (top) was VTK {4,5,7,6} -> basix {4,5,6,7} }}; /// Prism: 5 faces (2 triangles + 3 quads) -/// Represented as max 4 vertices per face, with -1 padding for triangles +/// Represented as max 4 vertices per face, with -1 padding for triangles. +/// Quad faces use basix tensor-product ordering: 0=(0,0),1=(1,0),2=(0,1),3=(1,1). +/// Conversion from VTK cyclic {a,b,c,d} -> basix {a,b,d,c}. inline constexpr std::array, 5> prism_faces = {{ {0, 1, 2, -1}, // bottom triangle {3, 4, 5, -1}, // top triangle - {0, 1, 4, 3}, // quad face - {1, 2, 5, 4}, // quad face - {2, 0, 3, 5} // quad face + {0, 1, 3, 4}, // quad (y=0 plane) was VTK {0,1,4,3} + {1, 2, 4, 5}, // quad (x+y=1 plane) was VTK {1,2,5,4} + {2, 0, 5, 3} // quad (x=0 plane) was VTK {2,0,3,5} }}; /// Pyramid: 5 faces (1 quad base + 4 triangles) +/// Base quad uses basix tensor-product ordering: 0=(0,0),1=(1,0),2=(0,1),3=(1,1). +/// Conversion from VTK cyclic {0,1,2,3} -> basix {0,1,3,2}. inline constexpr std::array, 5> pyramid_faces = {{ - {0, 1, 2, 3}, // base quad + {0, 1, 3, 2}, // base quad: basix ordering, was VTK {0,1,2,3} {0, 1, 4, -1}, // triangle {1, 2, 4, -1}, // triangle {2, 3, 4, -1}, // triangle @@ -227,4 +244,71 @@ inline constexpr int num_faces(type cell_type) } } +/// Get the number of vertices per face for a cell type. +inline std::span face_sizes(type cell_type) +{ + switch (cell_type) + { + case type::tetrahedron: return std::span(tetrahedron_face_sizes); + case type::hexahedron: return std::span(hexahedron_face_sizes); + case type::prism: return std::span(prism_face_sizes); + case type::pyramid: return std::span(pyramid_face_sizes); + default: + throw std::invalid_argument("Unknown cell type in face_sizes"); + } +} + +/// Get the vertex indices for face `face_id` of a tetrahedron. +/// Returns a span of 3 vertex indices. +inline std::span tet_face(int face_id) +{ + return std::span(tetrahedron_faces[static_cast(face_id)]); +} + +/// Get the cell type of a specific face of a cell. +inline type face_type(type cell_type, int face_id) +{ + switch (cell_type) + { + case type::tetrahedron: + return type::triangle; + case type::hexahedron: + return type::quadrilateral; + case type::prism: + return (face_id < 2) ? type::triangle : type::quadrilateral; + case type::pyramid: + return (face_id == 0) ? type::quadrilateral : type::triangle; + default: + throw std::invalid_argument("Unknown cell type in face_type"); + } +} + +/// Get the vertex indices for face `face_id` of a 3D cell. +/// The returned span has `face_sizes(cell_type)[face_id]` entries. +inline std::span face_vertices(type cell_type, int face_id) +{ + const int fsize = face_sizes(cell_type)[static_cast(face_id)]; + switch (cell_type) + { + case type::tetrahedron: + return std::span( + tetrahedron_faces[static_cast(face_id)].data(), + static_cast(fsize)); + case type::hexahedron: + return std::span( + hexahedron_faces[static_cast(face_id)].data(), + static_cast(fsize)); + case type::prism: + return std::span( + prism_faces[static_cast(face_id)].data(), + static_cast(fsize)); + case type::pyramid: + return std::span( + pyramid_faces[static_cast(face_id)].data(), + static_cast(fsize)); + default: + throw std::invalid_argument("Unknown cell type in face_vertices"); + } +} + } // namespace cutcells::cell diff --git a/cpp/src/cell_types.h b/cpp/src/cell_types.h index 71c9450..cfbcf7b 100644 --- a/cpp/src/cell_types.h +++ b/cpp/src/cell_types.h @@ -15,7 +15,7 @@ namespace cutcells namespace cell { /// Cell type - enum class type + enum class type : int { point = 0, interval = 1, diff --git a/cpp/src/cut_cell.cpp b/cpp/src/cut_cell.cpp index 01ba9de..18d27d9 100644 --- a/cpp/src/cut_cell.cpp +++ b/cpp/src/cut_cell.cpp @@ -14,14 +14,41 @@ #include "cut_interval.h" #include "cell_flags.h" #include "cell_subdivision.h" +#include "cell_topology.h" #include "utils.h" #include #include #include -#include +#include #include +namespace +{ + struct MergeVertexKey + { + uint8_t kind = 0; + int32_t a = 0; + int32_t b = 0; + + bool operator==(const MergeVertexKey& other) const noexcept + { + return kind == other.kind && a == other.a && b == other.b; + } + }; + + struct MergeVertexKeyHash + { + std::size_t operator()(const MergeVertexKey& k) const noexcept + { + const std::size_t h0 = std::hash{}(k.a); + const std::size_t h1 = std::hash{}(k.b); + const std::size_t hk = std::hash{}(k.kind); + return h0 ^ (h1 + 0x9e3779b97f4a7c15ULL + (h0 << 6) + (h0 >> 2)) ^ (hk << 1); + } + }; +} + namespace cutcells::cell{ @@ -38,16 +65,18 @@ namespace cutcells::cell{ std::cout << "]" << std::endl; std::cout << "connectivity=["; - for(int i=0;i &cut_cell, const int& id, std::vector& vertex_coordinates) { int gdim = cut_cell._gdim; - int num_vertices = cut_cell._connectivity[id].size(); + const auto vertices = cell_vertices(cut_cell, id); + int num_vertices = vertices.size(); vertex_coordinates.resize(num_vertices*gdim); int local_vertex_id = 0; for(std::size_t j=0;j + void sub_cell_vertices_phys(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates) + { + int gdim = cut_cell._gdim; + const auto vertices = cell_vertices(cut_cell, id); + int num_vertices = vertices.size(); + vertex_coordinates.resize(num_vertices*gdim); + int local_vertex_id = 0; + + for(std::size_t j=0;j T volume(const CutCell &cut_cell) { int gdim = cut_cell._gdim; T vol = 0; - std::size_t num_elements=cut_cell._connectivity.size(); + std::size_t num_elements=num_cells(cut_cell); for(std::size_t el = 0; el < num_elements; el++) { @@ -146,6 +196,13 @@ namespace cutcells::cell{ default: throw std::invalid_argument("Only intervals, triangles, quadrilaterals, tetrahedra, hexahedra, prisms and pyramids are implemented for cutting so far."); break; } + // Ensure parent meta-data is always set so mapping functions can use it. + cut_cell._parent_cell_type = cell_type; + if (cut_cell._parent_vertex_coords.empty()) + { + cut_cell._parent_vertex_coords.assign( + vertex_coordinates.begin(), vertex_coordinates.end()); + } } //Cutting of 2nd order triangles (6-node) and tetrahedra (10-node) @@ -191,13 +248,13 @@ namespace cutcells::cell{ for(std::size_t i=0;i sub_tet; + std::span sub_tet; switch(cell_type) { - case cutcells::cell::type::triangle: {sub_tet = cutcells::cell::triangle_subdivision_table[i]; + case cutcells::cell::type::triangle: {sub_tet = std::span(cutcells::cell::triangle_subdivision_table[i]); break;} - case cutcells::cell::type::tetrahedron: {sub_tet = cutcells::cell::tetrahedron_subdivision_table[i]; + case cutcells::cell::type::tetrahedron: {sub_tet = std::span(cutcells::cell::tetrahedron_subdivision_table[i]); break;} } @@ -281,19 +338,25 @@ namespace cutcells::cell{ merged_cut_cell._tdim=tdim; //Count the total number of cells in vector - int num_cells =0; + int total_num_cells =0; for(auto & cut_cell : cut_cell_vec) { - num_cells += cut_cell._connectivity.size(); + total_num_cells += cutcells::cell::num_cells(cut_cell); } - merged_cut_cell._connectivity.resize(num_cells); - merged_cut_cell._types.resize(num_cells); + merged_cut_cell._offset.resize(1); + merged_cut_cell._offset[0] = 0; int merged_vertex_id = 0; - int sub_cell_offset=0; int vertex_counter = 0; + std::size_t total_cut_vertices = 0; + for (const auto& cut_cell : cut_cell_vec) + total_cut_vertices += cut_cell._vertex_coords.size() / gdim; + + std::unordered_map merged_vertex_ids; + merged_vertex_ids.reserve(total_cut_vertices); + //all cutcells in vector above should have the same gdim and tdim for(auto & cut_cell : cut_cell_vec) { @@ -311,47 +374,124 @@ namespace cutcells::cell{ int num_cut_cell_vertices = cut_cell._vertex_coords.size()/gdim; - int local_num_cells = cut_cell._connectivity.size(); + int local_num_cells = num_cells(cut_cell); //Map from vertex id in current cutcell to merged cutcell - std::map local_merged_vertex_ids; + std::vector local_merged_vertex_ids(num_cut_cell_vertices, -1); + + const bool has_tokens = (static_cast(cut_cell._vertex_parent_entity.size()) == num_cut_cell_vertices); + const int parent_vertices = get_num_vertices(cut_cell._parent_cell_type); + const bool has_parent_ids = (static_cast(cut_cell._parent_vertex_ids.size()) == parent_vertices); + const bool can_fast_dedup = has_tokens && has_parent_ids; + const int32_t parent_discriminator = has_parent_ids ? static_cast(cut_cell._parent_vertex_ids[0]) : -1; + std::span> parent_edges; + if (can_fast_dedup) + parent_edges = edges(cut_cell._parent_cell_type); for(int local_id=0;local_id(merged_cut_cell._vertex_coords, cut_cell._vertex_coords, local_id, gdim); - - if(id==-1) //not found + if (can_fast_dedup) { - //add vertex - merged_vertex_id=vertex_counter; - vertex_counter++; + const int32_t token = cut_cell._vertex_parent_entity[local_id]; + MergeVertexKey key; - for(int j=0;j= 100 && token < 200) { - merged_cut_cell._vertex_coords.push_back(cut_cell._vertex_coords[local_id*gdim+j]); + const int ref_vid = static_cast(token - 100); + if (ref_vid >= 0 && ref_vid < parent_vertices) + { + key.kind = 0; + key.a = static_cast(cut_cell._parent_vertex_ids[ref_vid]); + key.b = 0; + } + else + { + key.kind = 2; + key.a = parent_discriminator; + key.b = token; + } + } + else if (token >= 200) + { + key.kind = 2; + key.a = parent_discriminator; + key.b = static_cast(token - 200); + } + else + { + const int edge_id = static_cast(token); + if (edge_id >= 0 && edge_id < static_cast(parent_edges.size())) + { + const int v0 = parent_edges[edge_id][0]; + const int v1 = parent_edges[edge_id][1]; + const int32_t gv0 = static_cast(cut_cell._parent_vertex_ids[v0]); + const int32_t gv1 = static_cast(cut_cell._parent_vertex_ids[v1]); + key.kind = 1; + key.a = std::min(gv0, gv1); + key.b = std::max(gv0, gv1); + } + else + { + key.kind = 2; + key.a = parent_discriminator; + key.b = token; + } + } + + const auto it = merged_vertex_ids.find(key); + if (it == merged_vertex_ids.end()) + { + merged_vertex_id=vertex_counter; + vertex_counter++; + + for(int j=0;jsecond; } } - else //found + else { - //take already existing vertex for local mapping - merged_vertex_id = id; + //check if vertex already exists to avoid doubling of vertices + int id = cutcells::utils::vertex_exists(merged_cut_cell._vertex_coords, cut_cell._vertex_coords, local_id, gdim); + + if(id==-1) //not found + { + //add vertex + merged_vertex_id=vertex_counter; + vertex_counter++; + + for(int j=0;j remapped_scratch; + const int nv = static_cast(vertices.size()); + for(int j=0;j> cut_cells(num_cells); + std::vector> cut_cells(total_num_cells); // std::cout << "ls_vals calculated " << ls_vals_all.size() << std::endl; // std::cout << "num_cells= " << num_cells << std::endl; @@ -403,9 +545,10 @@ namespace cutcells::cell{ int cut_cell_id = 0; //iterate over cells in cut_cell and cut each one - for(std::size_t j=0;j vertex_coords(num_vertices*gdim); @@ -413,7 +556,7 @@ namespace cutcells::cell{ for(std::size_t k=0;k &cut_cell); template void str(const CutCell &cut_cell); + template void sub_cell_vertices(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + template void sub_cell_vertices(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + + template void sub_cell_vertices_phys(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + template void sub_cell_vertices_phys(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + template double volume(const CutCell &cut_cell); template float volume(const CutCell &cut_cell); @@ -510,4 +659,4 @@ namespace cutcells::cell{ //----------------------------------------------------------------------------- -}//end of namespace \ No newline at end of file +}//end of namespace diff --git a/cpp/src/cut_cell.h b/cpp/src/cut_cell.h index 98ac8f0..ef67626 100644 --- a/cpp/src/cut_cell.h +++ b/cpp/src/cut_cell.h @@ -30,12 +30,23 @@ namespace cutcells /// Topological Dimension of Cell int _tdim; - /// Coordinates of vertices of cut cell + /// Cut vertices as produced by the cutting routine. + /// Semantic: always interpreted as parent reference coordinates after frame completion. + /// When cutting is done in reference space these are ready to use. + /// When cutting is done in physical space, call complete_from_physical() which + /// copies these into _vertex_coords_phys and pulls back to fill _vertex_coords. std::vector _vertex_coords; + /// Cut vertices in physical space. + /// Empty until compute_physical_cut_vertices() or complete_from_physical() is called. + std::vector _vertex_coords_phys; + /// Vertex ids of cut cells - /// @todo: maybe change this to connectivity and offset vectors - std::vector> _connectivity; + /// Flattened cell-to-vertex connectivity in CSR layout + std::vector _connectivity; + + /// Offsets into _connectivity (size = num_cells + 1) + std::vector _offset; /// Cell type of cut cells std::vector _types; @@ -64,6 +75,12 @@ namespace cutcells template void sub_cell_vertices(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + /// Gather physical-space coordinates of sub-cell id into vertex_coordinates. + /// Reads from _vertex_coords_phys, which must be filled before calling + /// (call compute_physical_cut_vertices or complete_from_physical first). + template + void sub_cell_vertices_phys(const CutCell &cut_cell, const int& id, std::vector& vertex_coordinates); + template T volume(const CutCell &cut_cell); @@ -93,6 +110,72 @@ namespace cutcells std::span ls_vals_all, const std::string& cut_type_str, bool triangulate); + + template + inline int num_cells(const CutCell& cut_cell) + { + return cut_cell._offset.empty() ? 0 : static_cast(cut_cell._offset.size()) - 1; + } + + template + inline int num_cell_vertices(const CutCell& cut_cell, const int cell_id) + { + return cut_cell._offset[cell_id + 1] - cut_cell._offset[cell_id]; + } + + template + inline std::span cell_vertices(const CutCell& cut_cell, const int cell_id) + { + const int begin = cut_cell._offset[cell_id]; + const int end = cut_cell._offset[cell_id + 1]; + return std::span(cut_cell._connectivity.data() + begin, end - begin); + } + + template + inline void clear_cell_topology(CutCell& cut_cell) + { + cut_cell._connectivity.clear(); + cut_cell._offset.clear(); + cut_cell._offset.push_back(0); + cut_cell._types.clear(); + } + + template + inline void reserve_cell_topology(CutCell& cut_cell, + const int connectivity_capacity, + const int cell_capacity) + { + cut_cell._connectivity.reserve(connectivity_capacity); + cut_cell._offset.reserve(cell_capacity + 1); + cut_cell._types.reserve(cell_capacity); + } + + template + inline void append_cell(CutCell& cut_cell, const type cell_type, std::span vertices) + { + cut_cell._types.push_back(cell_type); + cut_cell._connectivity.insert(cut_cell._connectivity.end(), vertices.begin(), vertices.end()); + cut_cell._offset.push_back(static_cast(cut_cell._connectivity.size())); + } + + /// Append a subcell given as a raw pointer + count. + /// Avoids forcing a temporary std::vector at call sites. + template + inline void append_cell(CutCell& cut_cell, const type cell_type, const int* vertices, int n) + { + cut_cell._types.push_back(cell_type); + cut_cell._connectivity.insert(cut_cell._connectivity.end(), vertices, vertices + n); + cut_cell._offset.push_back(static_cast(cut_cell._connectivity.size())); + } + + /// Append a subcell given as a fixed-size std::array using the first n entries. + template + inline void append_cell(CutCell& cut_cell, const type cell_type, const std::array& vertices, int n) + { + cut_cell._types.push_back(cell_type); + cut_cell._connectivity.insert(cut_cell._connectivity.end(), vertices.begin(), vertices.begin() + n); + cut_cell._offset.push_back(static_cast(cut_cell._connectivity.size())); + } } } \ No newline at end of file diff --git a/cpp/src/cut_hexahedron.cpp b/cpp/src/cut_hexahedron.cpp index 8ba5c20..d94fbf9 100644 --- a/cpp/src/cut_hexahedron.cpp +++ b/cpp/src/cut_hexahedron.cpp @@ -17,42 +17,46 @@ #include #include #include -#include #include namespace cutcells::cell::hexahedron { namespace { + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 40; + constexpr int reserve_connectivity = 64; + constexpr int reserve_types = 64; + [[noreturn]] void throw_missing_token(const int flag, const int token, const char* where) { throw std::runtime_error(std::string(where) + ": missing token=" + std::to_string(token) + " for flag=" + std::to_string(flag)); } - int lookup_token_or_throw(const std::unordered_map& vertex_case_map, + int lookup_token_or_throw(const VertexCaseMap& vertex_case_map, const int flag, const int token, const char* where) { - const auto it = vertex_case_map.find(token); - if (it == vertex_case_map.end()) + if (token < 0 || token >= static_cast(vertex_case_map.size()) || vertex_case_map[token] < 0) throw_missing_token(flag, token, where); - return it->second; + return vertex_case_map[token]; } template void compute_intersection_points(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { intersection_points.clear(); - vertex_case_map.clear(); + intersection_points.reserve(12 * gdim); + vertex_case_map.fill(-1); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector ip(gdim); + // gdim is 3 for hexahedra; use stack arrays to avoid heap allocation per edge + std::array v0 = {}; + std::array v1 = {}; int ip_index = 0; for (int e = 0; e < 12; ++e) @@ -72,10 +76,11 @@ namespace cutcells::cell::hexahedron const T ls0 = ls_values[v0_id]; const T ls1 = ls_values[v1_id]; - interval::compute_intersection_point(0.0, v0, v1, ls0, ls1, ip); - - for (int j = 0; j < gdim; ++j) - intersection_points.push_back(ip[j]); + const int ip_offset = static_cast(intersection_points.size()); + intersection_points.resize(intersection_points.size() + gdim); + interval::compute_intersection_point(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip_offset); vertex_case_map[e] = ip_index; ++ip_index; @@ -86,9 +91,9 @@ namespace cutcells::cell::hexahedron void ensure_vertex_token(const std::span vertex_coordinates, const int gdim, const int token, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; const int vid = token - 100; @@ -106,9 +111,9 @@ namespace cutcells::cell::hexahedron const int* special_point_offset, const int* special_point_data, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; const int sp_id = token - 200; @@ -127,7 +132,8 @@ namespace cutcells::cell::hexahedron if (nrefs <= 0) throw std::runtime_error("Malformed special point definition"); - std::vector coord(gdim, T(0)); + // gdim is at most 3 for 3-D cells; use stack storage + std::array coord = {T(0), T(0), T(0)}; for (int r = 0; r < nrefs; ++r) { const int ref = special_point_data[idx++]; @@ -179,7 +185,7 @@ namespace cutcells::cell::hexahedron const int* special_point_data, bool triangulate, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { const int cell_begin = case_offsets[flag]; const int cell_end = case_offsets[flag + 1]; @@ -189,8 +195,8 @@ namespace cutcells::cell::hexahedron const type sub_type = cell_types[cell_idx]; const int* verts = subcell_verts[cell_idx]; - std::vector verts_local; - verts_local.reserve(MaxVerts); + std::array verts_local; + int nverts = 0; for (int i = 0; i < MaxVerts; ++i) { const int token = verts[i]; @@ -212,20 +218,19 @@ namespace cutcells::cell::hexahedron cut_cell, vertex_case_map); } - verts_local.push_back(lookup_token_or_throw(vertex_case_map, flag, token, "decode_case")); + verts_local[nverts++] = lookup_token_or_throw(vertex_case_map, flag, token, "decode_case"); } - if (triangulate && sub_type == type::quadrilateral && verts_local.size() == 4) + if (triangulate && sub_type == type::quadrilateral && nverts == 4) { - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[1], verts_local[2]}); - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[2], verts_local[3]}); + const std::array t0 = {verts_local[0], verts_local[1], verts_local[2]}; + const std::array t1 = {verts_local[0], verts_local[2], verts_local[3]}; + cutcells::cell::append_cell(cut_cell, type::triangle, t0, 3); + cutcells::cell::append_cell(cut_cell, type::triangle, t1, 3); } else { - cut_cell._types.push_back(sub_type); - cut_cell._connectivity.push_back(std::move(verts_local)); + cutcells::cell::append_cell(cut_cell, sub_type, verts_local.data(), nverts); } } } @@ -260,15 +265,16 @@ namespace cutcells::cell::hexahedron } // Compute intersections (shared for all parts) - std::vector intersection_points; - std::unordered_map vertex_case_map; + thread_local std::vector intersection_points; + VertexCaseMap vertex_case_map; compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_lt0, - intersection_points, vertex_case_map); + intersection_points, vertex_case_map); cut_cell._gdim = gdim; - cut_cell._vertex_coords = std::move(intersection_points); - cut_cell._types.clear(); - cut_cell._connectivity.clear(); + cut_cell._vertex_coords.assign(intersection_points.begin(), intersection_points.end()); + cutcells::cell::clear_cell_topology(cut_cell); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); if (cut_type_str == "phi=0") { @@ -299,7 +305,7 @@ namespace cutcells::cell::hexahedron throw std::invalid_argument("cutting type unknown"); } - cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity, 12, 8, 56); } template diff --git a/cpp/src/cut_interval.cpp b/cpp/src/cut_interval.cpp index 304592a..fe587e5 100644 --- a/cpp/src/cut_interval.cpp +++ b/cpp/src/cut_interval.cpp @@ -7,10 +7,11 @@ #include "cell_flags.h" #include "span_math.h" #include "utils.h" -#include namespace cutcells::cell { + using VertexCaseMap = std::array; + namespace{ int interval_sub_element[4][2] = { {-1 -1}, // 0 @@ -28,32 +29,10 @@ namespace cutcells::cell return 1; } - // cut interval by linear interpolation to obtain intersection point - template - void compute_intersection_point(const T& level, std::span p0, std::span p1, - const T& v0, const T& v1, std::vector& intersection_point, const int & offset) - { - // TODO: Catch precision errors and almost alignments. - for(int i=0;i>& interface_cells, std::vector& interface_cell_types) - { - interface_cells.resize(1); - interface_cells[0].resize(1); - interface_cells[0][0] = 0; - - interface_cell_types.resize(1); - interface_cell_types[0] = type::point; - } - - template +template void create_sub_cell_vertex_coords(const int& flag, const std::span vertex_coordinates, const int gdim, const std::span intersection_points, - std::vector& coords, std::unordered_map& vertex_case_map) + std::vector& coords, VertexCaseMap& vertex_case_map) { int num_intersection_points = intersection_points.size()/gdim; type cell_type = cell::type::interval; @@ -96,28 +75,6 @@ namespace cutcells::cell } } - void create_sub_cells(const int& flag, std::vector>& sub_cells, - std::vector& sub_cell_types, std::unordered_map& vertex_case_map) - { - // Allocate memory - int num_sub_elements = 1; - sub_cells.resize(num_sub_elements); - sub_cell_types.resize(num_sub_elements); - - for(int i=0;i void create_cut_cell(const std::span vertex_coordinates, const int gdim, const std::span ls_values, @@ -126,7 +83,9 @@ namespace cutcells::cell const std::span intersection_points) { cut_cell._gdim = gdim; - std::unordered_map vertex_case_map; + cutcells::cell::clear_cell_topology(cut_cell); + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); if(cut_type_str=="phi=0") { @@ -136,28 +95,33 @@ namespace cutcells::cell { cut_cell._vertex_coords[i] = intersection_points[i]; } - //Fill in cut cell with intersection point for interval this is just the intersection point - create_interface_cells(cut_cell._connectivity, cut_cell._types); + // Interface is the single intersection point (index 0) + const int v = 0; + cutcells::cell::append_cell(cut_cell, type::point, &v, 1); } else if(cut_type_str=="phi<0") { cut_cell._tdim = 1; int flag_interior = get_entity_flag(ls_values, false); - create_sub_cell_vertex_coords(flag_interior, vertex_coordinates, gdim, intersection_points, + create_sub_cell_vertex_coords(flag_interior, vertex_coordinates, gdim, intersection_points, cut_cell._vertex_coords, vertex_case_map); - //Determine interior sub-cells - create_sub_cells(flag_interior, cut_cell._connectivity, - cut_cell._types, vertex_case_map); + // Interval always produces exactly one sub-interval with 2 vertices + std::array verts; + verts[0] = vertex_case_map[interval_sub_element[flag_interior][0]]; + verts[1] = vertex_case_map[interval_sub_element[flag_interior][1]]; + cutcells::cell::append_cell(cut_cell, type::interval, verts, 2); } else if(cut_type_str=="phi>0") { cut_cell._tdim = 1; - //Determine exterior sub-cells int flag_exterior = get_entity_flag(ls_values, true); - create_sub_cell_vertex_coords(flag_exterior, vertex_coordinates, gdim, intersection_points, + create_sub_cell_vertex_coords(flag_exterior, vertex_coordinates, gdim, intersection_points, cut_cell._vertex_coords, vertex_case_map); - create_sub_cells(flag_exterior, cut_cell._connectivity, - cut_cell._types, vertex_case_map); + // Interval always produces exactly one sub-interval with 2 vertices + std::array verts; + verts[0] = vertex_case_map[interval_sub_element[flag_exterior][0]]; + verts[1] = vertex_case_map[interval_sub_element[flag_exterior][1]]; + cutcells::cell::append_cell(cut_cell, type::interval, verts, 2); } else { @@ -182,9 +146,10 @@ namespace cutcells::cell } // Compute intersection point these are required for any cut cell part (interface, interior, exterior) - std::vector intersection_point(gdim); - std::vector p0(gdim); - std::vector p1(gdim); + thread_local std::vector intersection_point; + intersection_point.resize(gdim); + std::array p0 = {}; + std::array p1 = {}; T level = 0.0; for(auto i=0;i(level, p0, p1,ls_values[0],ls_values[1], intersection_point); + compute_intersection_point(level, std::span(p0.data(), gdim), + std::span(p1.data(), gdim), + ls_values[0], ls_values[1], intersection_point); //Create the cut cell depending on which cut is requested create_cut_cell(vertex_coordinates, gdim, ls_values, cut_type_str, cut_cell, intersection_point); @@ -217,11 +184,6 @@ namespace cutcells::cell const std::span ls_values, const std::string& cut_type_str, CutCell& cut_cell); - template void compute_intersection_point(const double& level, std::span p0, std::span p1, - const double& v0, const double& v1, std::vector& intersection_point, const int & offset); - template void compute_intersection_point(const float& level, std::span p0, std::span p1, - const float& v0, const float& v1, std::vector& intersection_point, const int & offset); - template double volume(const std::span vertex_coordinates, const int gdim); template float volume(const std::span vertex_coordinates, const int gdim); diff --git a/cpp/src/cut_interval.h b/cpp/src/cut_interval.h index 1d573e1..1e89c93 100644 --- a/cpp/src/cut_interval.h +++ b/cpp/src/cut_interval.h @@ -14,9 +14,14 @@ namespace cutcells::cell { namespace interval{ // cut interval by linear interpolation to obtain intersection point + // Defined inline so compilers can inline the call at each use site. template - void compute_intersection_point(const T &level, std::span p0, std::span p1, - const T& v0, const T& v1, std::vector& intersection_point, const int & offset=0); + inline void compute_intersection_point(const T& level, std::span p0, std::span p1, + const T& v0, const T& v1, std::vector& intersection_point, const int offset = 0) + { + for (int i = 0; i < static_cast(p0.size()); ++i) + intersection_point[i + offset] = p0[i] + (p1[i] - p0[i]) * (level - v0) / (v1 - v0); + } template void cut(const std::span vertex_coordinates, const int gdim, diff --git a/cpp/src/cut_mesh.cpp b/cpp/src/cut_mesh.cpp index daa0ac7..b742bb7 100644 --- a/cpp/src/cut_mesh.cpp +++ b/cpp/src/cut_mesh.cpp @@ -11,6 +11,7 @@ #include "utils.h" #include "cell_topology.h" +#include "reference_cell.h" #include #include #include @@ -49,8 +50,7 @@ namespace int local_vertex_id, int gdim) { const int base = local_vertex_id * gdim; - for (int j = 0; j < gdim; ++j) - out.push_back(in[base + j]); + out.insert(out.end(), in.data() + base, in.data() + base + gdim); } } @@ -74,12 +74,14 @@ namespace cutcells::mesh } std::cout << "]" << std::endl; std::cout << "connectivity=["; - for(int i=0;i 0) + num_connectivity += cut_cell._offset.back(); } - int num_connectivity=0; - for(auto & cut_cell : cut_cells._cut_cells) - for(int i=0;i global_vertex_ids; - global_vertex_ids.reserve(static_cast(num_connectivity)); + // Adaptive dedup map: flat linear-scan vector for small meshes (typ. a + // handful of cut cells — fast due to cache locality and no hashing), + // falling back to an unordered_map for larger meshes. + // Both are thread_local so their allocations are amortised across calls. + constexpr int kFlatMapThreshold = 128; + const bool use_flat = (num_connectivity <= kFlatMapThreshold); + + thread_local std::vector> flat_vertex_ids; + thread_local std::unordered_map hash_vertex_ids; + + flat_vertex_ids.clear(); + if (!use_flat) + { + hash_vertex_ids.clear(); + hash_vertex_ids.reserve(static_cast(num_connectivity)); + } + + // Helper lambdas that abstract over the two map implementations. + // find_vertex: returns the merged id, or -1 if not yet seen. + // insert_vertex: adds a new entry; only called when find returns -1. + auto find_vertex = [&](const VertexKey& key) -> int + { + if (use_flat) + { + for (const auto& [k, v] : flat_vertex_ids) + if (k == key) return v; + return -1; + } + const auto it = hash_vertex_ids.find(key); + return it == hash_vertex_ids.end() ? -1 : it->second; + }; + auto insert_vertex = [&](const VertexKey& key, int id) + { + if (use_flat) + flat_vertex_ids.emplace_back(key, id); + else + hash_vertex_ids.emplace(key, id); + }; //all cutcells in vector above should have the same gdim and tdim for(auto & cut_cell : cut_cells._cut_cells) @@ -197,10 +236,12 @@ namespace cutcells::mesh int num_cut_cell_vertices = cut_cell._vertex_coords.size()/gdim; - int local_num_cells = cut_cell._connectivity.size(); + int local_num_cells = cutcells::cell::num_cells(cut_cell); // Map from vertex id in current cutcell to merged cutmesh - std::vector local_merged_vertex_ids(num_cut_cell_vertices, -1); + // thread_local: reuse the underlying storage each call to avoid per-cell allocation. + thread_local std::vector local_merged_vertex_ids; + local_merged_vertex_ids.assign(num_cut_cell_vertices, -1); // Determine whether we can use fast token-based dedup for this cut cell. const bool has_tokens = (static_cast(cut_cell._vertex_parent_entity.size()) == num_cut_cell_vertices); @@ -254,27 +295,32 @@ namespace cutcells::mesh } else { - const int v0 = parent_edges[edge_id][0]; - const int v1 = parent_edges[edge_id][1]; - const int32_t gv0 = static_cast(cut_cell._parent_vertex_ids[v0]); - const int32_t gv1 = static_cast(cut_cell._parent_vertex_ids[v1]); + // token is a VTK edge index; cell_topology.h uses Basix ordering. + const int basix_eid = cell::vtk_to_basix_edge(cut_cell._parent_cell_type, edge_id); + const int bv0 = parent_edges[basix_eid][0]; + const int bv1 = parent_edges[basix_eid][1]; + // _parent_vertex_ids is indexed by VTK vertex; convert. + const int vtk_v0 = cell::basix_to_vtk_vertex(cut_cell._parent_cell_type, bv0); + const int vtk_v1 = cell::basix_to_vtk_vertex(cut_cell._parent_cell_type, bv1); + const int32_t gv0 = static_cast(cut_cell._parent_vertex_ids[vtk_v0]); + const int32_t gv1 = static_cast(cut_cell._parent_vertex_ids[vtk_v1]); key.kind = 1; key.a = std::min(gv0, gv1); key.b = std::max(gv0, gv1); } } - auto it = global_vertex_ids.find(key); - if (it == global_vertex_ids.end()) + const int existing = find_vertex(key); + if (existing == -1) { const int merged_vertex_id = static_cast(cut_mesh._vertex_coords.size() / gdim); append_vertex_coords(cut_mesh._vertex_coords, cut_cell._vertex_coords, local_id, static_cast(gdim)); - global_vertex_ids.emplace(key, merged_vertex_id); + insert_vertex(key, merged_vertex_id); local_merged_vertex_ids[local_id] = merged_vertex_id; } else { - local_merged_vertex_ids[local_id] = it->second; + local_merged_vertex_ids[local_id] = existing; } } } @@ -300,10 +346,11 @@ namespace cutcells::mesh for(int i=0;i(index)]; } @@ -313,7 +360,11 @@ namespace cutcells::mesh cut_mesh._types[sub_cell_offset+i]=cut_cell._types[i]; for(int j=0;j cells; + std::vector level_set_values; int num_cells = vtk_type.size(); for(std::size_t i=0;i level_set_values(num_vertices); + if (static_cast(level_set_values.size()) != num_vertices) + level_set_values.resize(num_vertices); + for(std::size_t j=0;j cut_cells; + // ----------------------------------------------------------------------- + // Fused single-pass implementation. + // + // All scratch buffers are thread_local so their heap allocations are + // amortised across calls (vectors keep their capacity; the hash map keeps + // its bucket array). No intermediate CutCells accumulator is created, + // and no CutCell is ever copy-assigned — the cut writes directly into a + // reusable scratch object that is immediately merged into the output. + // ----------------------------------------------------------------------- + + // --- thread-local scratch (reused across invocations) ------------------ + thread_local std::vector tl_intersected; + thread_local std::vector tl_vtx_buf; + thread_local std::vector tl_ls_buf; + thread_local cell::CutCell tl_scratch; + thread_local std::vector tl_local_merged; + thread_local std::unordered_map tl_dedup; + + // --- Pass 1: locate intersected cells ---------------------------------- + tl_intersected.clear(); + { + const int ncells = static_cast(vtk_type.size()); + for (int i = 0; i < ncells; ++i) + { + const int coff = offset[i]; + const cell::type ctype = cell::map_vtk_type_to_cell_type( + static_cast(vtk_type[i])); + const int nv = cell::get_num_vertices(ctype); + tl_ls_buf.resize(nv); + for (int j = 0; j < nv; ++j) + tl_ls_buf[j] = ls_vals[connectivity[coff + j]]; + if (cell::classify_cell_domain(std::span(tl_ls_buf.data(), nv)) + == cell::domain::intersected) + tl_intersected.push_back(i); + } + } - auto intersected_cells = locate_cells(ls_vals, points, - connectivity, offset, - vtk_type, - cell::cut_type::phieq0); + if (tl_intersected.empty()) + return CutMesh{}; + + const int n_cut = static_cast(tl_intersected.size()); + + // --- Prepare output mesh ----------------------------------------------- + // Heuristic reserves: each intersected cell produces ~8 sub-cells, + // ~4 vertices each (tet / triangle). + CutMesh cm; + cm._gdim = 3; // VTK meshes always use 3-D coordinates + cm._tdim = 0; // filled from first non-empty cut result + cm._num_cells = 0; + cm._num_vertices = 0; + cm._types.reserve(n_cut * 8); + cm._offset.reserve(n_cut * 8 + 1); + cm._connectivity.reserve(n_cut * 8 * 4); + cm._parent_map.reserve(n_cut * 8); + cm._vertex_coords.reserve(n_cut * 12); + cm._offset.push_back(0); + + // --- Clear dedup map (bucket array is retained across calls) ----------- + tl_dedup.clear(); + tl_dedup.reserve(static_cast(n_cut * 10)); + + int total_conn = 0; + int total_cells = 0; + + // --- Pass 2: cut each cell and immediately merge into cm --------------- + for (int i = 0; i < n_cut; ++i) + { + const int ci = tl_intersected[i]; + const int coff = offset[ci]; + const cell::type ctype = cell::map_vtk_type_to_cell_type( + static_cast(vtk_type[ci])); + const int nv = cell::get_num_vertices(ctype); + + // Gather vertex coords and level-set values + tl_vtx_buf.resize(nv * 3); + tl_ls_buf.resize(nv); + for (int j = 0; j < nv; ++j) + { + const int vid = connectivity[coff + j]; + tl_vtx_buf[j * 3 + 0] = points[vid * 3 + 0]; + tl_vtx_buf[j * 3 + 1] = points[vid * 3 + 1]; + tl_vtx_buf[j * 3 + 2] = points[vid * 3 + 2]; + tl_ls_buf[j] = ls_vals[vid]; + } - cut_cells._cut_cells.resize(intersected_cells.size()); - cut_cells._parent_map.resize(intersected_cells.size()); + // Cut into scratch — all CutCell fields are reset by the cutter + cell::cut(ctype, tl_vtx_buf, 3, tl_ls_buf, cut_type_str, tl_scratch, triangulate); - for(std::size_t i=0;i(vtk_type[cell_index]); - cell::type cell_type = cell::map_vtk_type_to_cell_type(cell_vtk_type); - int num_vertices = cell::get_num_vertices(cell_type); + // Override parent vertex IDs with context-global mesh indices + // (the individual cutters default them to local 0..nv-1) + tl_scratch._parent_cell_type = ctype; + tl_scratch._parent_vertex_ids.resize(nv); + for (int j = 0; j < nv; ++j) + tl_scratch._parent_vertex_ids[j] = connectivity[coff + j]; - std::vector vertex_coords(num_vertices*3); - std::vector level_set_values(num_vertices); + const int n_local_verts = static_cast(tl_scratch._vertex_coords.size()) + / tl_scratch._gdim; + const int n_local_cells = cell::num_cells(tl_scratch); + if (n_local_cells == 0) + continue; - for(std::size_t j=0;j(tl_scratch._vertex_parent_entity.size()) + == n_local_verts); - cell::CutCell cut_cell; + if (has_tokens) + { + // Fast token-based dedup: shared vertices between neighbouring cut + // cells (parent vertices and edge intersections) are identified via + // their VertexKey and mapped to a single global vertex. + const auto parent_edges = cell::edges(ctype); - //cut the cell - cell::cut(cell_type, vertex_coords,3,level_set_values,cut_type_str,cut_cell,triangulate); + for (int lv = 0; lv < n_local_verts; ++lv) + { + const int32_t token = tl_scratch._vertex_parent_entity[lv]; + VertexKey key; - // Populate context-global parent vertex IDs to enable fast merging in create_cut_mesh. - // The cutters themselves default these IDs to 0..V-1. - cut_cell._parent_cell_type = cell_type; - cut_cell._parent_vertex_ids.resize(num_vertices); - for (int j = 0; j < num_vertices; ++j) + if (token >= 100 && token < 200) + { + const int ref = token - 100; + if (ref >= 0 && ref < nv) + { + key.kind = 0; + key.a = static_cast(tl_scratch._parent_vertex_ids[ref]); + key.b = 0; + } + else { key.kind = 2; key.a = ci; key.b = token; } + } + else if (token >= 200) + { + key.kind = 2; + key.a = ci; + key.b = static_cast(token - 200); + } + else + { + const int eid = static_cast(token); + if (eid >= 0 && eid < static_cast(parent_edges.size())) + { + // token is a VTK edge index; cell_topology.h uses Basix ordering. + const int basix_eid = cell::vtk_to_basix_edge(ctype, eid); + const int bv0 = parent_edges[basix_eid][0]; + const int bv1 = parent_edges[basix_eid][1]; + const int vtk_v0 = cell::basix_to_vtk_vertex(ctype, bv0); + const int vtk_v1 = cell::basix_to_vtk_vertex(ctype, bv1); + const int32_t gv0 = static_cast( + tl_scratch._parent_vertex_ids[vtk_v0]); + const int32_t gv1 = static_cast( + tl_scratch._parent_vertex_ids[vtk_v1]); + key.kind = 1; + key.a = std::min(gv0, gv1); + key.b = std::max(gv0, gv1); + } + else { key.kind = 2; key.a = ci; key.b = token; } + } + + const int next_id = static_cast(cm._vertex_coords.size()) / cm._gdim; + auto [it, inserted] = tl_dedup.emplace(key, next_id); + tl_local_merged[lv] = it->second; + if (inserted) + { + const int base = lv * tl_scratch._gdim; + cm._vertex_coords.insert(cm._vertex_coords.end(), + tl_scratch._vertex_coords.data() + base, + tl_scratch._vertex_coords.data() + base + tl_scratch._gdim); + } + } + } + else { - const int vertex_id = connectivity[cell_offset + j]; - cut_cell._parent_vertex_ids[j] = vertex_id; + // Fallback: tokens not available — emit all local vertices without + // inter-cell dedup (unusual path; occurs only without token support) + for (int lv = 0; lv < n_local_verts; ++lv) + { + tl_local_merged[lv] = static_cast(cm._vertex_coords.size()) / cm._gdim; + const int base = lv * tl_scratch._gdim; + cm._vertex_coords.insert(cm._vertex_coords.end(), + tl_scratch._vertex_coords.data() + base, + tl_scratch._vertex_coords.data() + base + tl_scratch._gdim); + } } - cut_cells._cut_cells[i] = cut_cell; - cut_cells._parent_map[i] = cell_index; - - if (std::find(cut_cells._types.begin(), cut_cells._types.end(), cell_type) == cut_cells._types.end()) - cut_cells._types.push_back(cell_type); + // Emit sub-cells + for (int lc = 0; lc < n_local_cells; ++lc) + { + const auto verts = cell::cell_vertices(tl_scratch, lc); + const int vcnt = static_cast(verts.size()); + for (int v = 0; v < vcnt; ++v) + cm._connectivity.push_back(tl_local_merged[verts[v]]); + total_conn += vcnt; + cm._offset.push_back(total_conn); + cm._types.push_back(tl_scratch._types[lc]); + cm._parent_map.push_back(ci); + ++total_cells; + } } - return create_cut_mesh(cut_cells); + cm._num_cells = total_cells; + cm._num_vertices = static_cast(cm._vertex_coords.size()) / cm._gdim; + + return cm; } //----------------------------------------------------------------------------- diff --git a/cpp/src/cut_mesh.h b/cpp/src/cut_mesh.h index 913fa1a..4fac90c 100644 --- a/cpp/src/cut_mesh.h +++ b/cpp/src/cut_mesh.h @@ -40,16 +40,16 @@ namespace cutcells::mesh struct CutMesh { /// Geometric Dimension of Cell - int _gdim; + int _gdim = 0; /// Topological Dimension of Cell - int _tdim; + int _tdim = 0; /// Number of cells - int _num_cells; + int _num_cells = 0; /// Number of vertices - int _num_vertices; + int _num_vertices = 0; /// Coordinates of vertices of cut cell std::vector _vertex_coords; diff --git a/cpp/src/cut_prism.cpp b/cpp/src/cut_prism.cpp index acc9faf..b8c1d8e 100644 --- a/cpp/src/cut_prism.cpp +++ b/cpp/src/cut_prism.cpp @@ -17,13 +17,17 @@ #include #include #include -#include #include namespace cutcells::cell::prism { namespace { + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 32; + constexpr int reserve_connectivity = 48; + constexpr int reserve_types = 48; + // VTK_WEDGE / CutCells prism vertex ordering assumed: // bottom tri: 0,1,2 ; top tri: 3,4,5. // Edge ids must match the VTK TableBasedClip case stream. @@ -39,29 +43,29 @@ namespace cutcells::cell::prism + " for flag=" + std::to_string(flag)); } - int lookup_token_or_throw(const std::unordered_map& vertex_case_map, + int lookup_token_or_throw(const VertexCaseMap& vertex_case_map, const int flag, const int token, const char* where) { - const auto it = vertex_case_map.find(token); - if (it == vertex_case_map.end()) + if (token < 0 || token >= static_cast(vertex_case_map.size()) || vertex_case_map[token] < 0) throw_missing_token(flag, token, where); - return it->second; + return vertex_case_map[token]; } template void compute_intersection_points(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { intersection_points.clear(); - vertex_case_map.clear(); + intersection_points.reserve(9 * gdim); + vertex_case_map.fill(-1); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector ip(gdim); + // gdim is 3 for prisms; use stack arrays to avoid heap allocation per edge + std::array v0 = {}; + std::array v1 = {}; int ip_index = 0; for (int e = 0; e < 9; ++e) @@ -81,10 +85,11 @@ namespace cutcells::cell::prism const T ls0 = ls_values[v0_id]; const T ls1 = ls_values[v1_id]; - interval::compute_intersection_point(0.0, v0, v1, ls0, ls1, ip); - - for (int j = 0; j < gdim; ++j) - intersection_points.push_back(ip[j]); + const int ip_offset = static_cast(intersection_points.size()); + intersection_points.resize(intersection_points.size() + gdim); + interval::compute_intersection_point(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip_offset); vertex_case_map[e] = ip_index; ++ip_index; @@ -95,9 +100,9 @@ namespace cutcells::cell::prism void ensure_vertex_token(const std::span vertex_coordinates, const int gdim, const int token, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; const int vid = token - 100; @@ -115,9 +120,9 @@ namespace cutcells::cell::prism const int* special_point_offset, const int* special_point_data, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; const int sp_id = token - 200; @@ -136,7 +141,8 @@ namespace cutcells::cell::prism if (nrefs <= 0) throw std::runtime_error("Malformed special point definition"); - std::vector coord(gdim, T(0)); + // gdim is at most 3 for 3-D cells; use stack storage + std::array coord = {T(0), T(0), T(0)}; for (int r = 0; r < nrefs; ++r) { const int ref = special_point_data[idx++]; @@ -187,7 +193,7 @@ namespace cutcells::cell::prism const int* special_point_data, bool triangulate, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { const int cell_begin = case_offsets[flag]; const int cell_end = case_offsets[flag + 1]; @@ -197,8 +203,8 @@ namespace cutcells::cell::prism const type sub_type = cell_types[cell_idx]; const int* verts = subcell_verts[cell_idx]; - std::vector verts_local; - verts_local.reserve(MaxVerts); + std::array verts_local; + int nverts = 0; for (int i = 0; i < MaxVerts; ++i) { const int token = verts[i]; @@ -220,20 +226,19 @@ namespace cutcells::cell::prism cut_cell, vertex_case_map); } - verts_local.push_back(lookup_token_or_throw(vertex_case_map, flag, token, "decode_case")); + verts_local[nverts++] = lookup_token_or_throw(vertex_case_map, flag, token, "decode_case"); } - if (triangulate && sub_type == type::quadrilateral && verts_local.size() == 4) + if (triangulate && sub_type == type::quadrilateral && nverts == 4) { - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[1], verts_local[2]}); - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[2], verts_local[3]}); + const std::array t0 = {verts_local[0], verts_local[1], verts_local[2]}; + const std::array t1 = {verts_local[0], verts_local[2], verts_local[3]}; + cutcells::cell::append_cell(cut_cell, type::triangle, t0, 3); + cutcells::cell::append_cell(cut_cell, type::triangle, t1, 3); } else { - cut_cell._types.push_back(sub_type); - cut_cell._connectivity.push_back(std::move(verts_local)); + cutcells::cell::append_cell(cut_cell, sub_type, verts_local.data(), nverts); } } } @@ -266,17 +271,19 @@ namespace cutcells::cell::prism } // Compute intersections (shared for all parts) - std::vector intersection_points; - std::unordered_map vertex_case_map; + thread_local std::vector intersection_points; + VertexCaseMap vertex_case_map; compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_lt0, - intersection_points, vertex_case_map); + intersection_points, vertex_case_map); cut_cell._gdim = gdim; - cut_cell._vertex_coords = std::move(intersection_points); - cut_cell._types.clear(); - cut_cell._connectivity.clear(); + cut_cell._vertex_coords.assign(intersection_points.begin(), intersection_points.end()); + cutcells::cell::clear_cell_topology(cut_cell); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); + const cut_type cut_kind = string_to_cut_type(cut_type_str); - if (cut_type_str == "phi=0") + if (cut_kind == cut_type::phieq0) { cut_cell._tdim = 2; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -284,7 +291,7 @@ namespace cutcells::cell::prism special_point_count_interface, special_point_offset_interface, special_point_data_interface, triangulate, cut_cell, vertex_case_map); } - else if (cut_type_str == "phi<0") + else if (cut_kind == cut_type::philt0) { cut_cell._tdim = 3; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -292,7 +299,7 @@ namespace cutcells::cell::prism special_point_count_inside, special_point_offset_inside, special_point_data_inside, triangulate, cut_cell, vertex_case_map); } - else if (cut_type_str == "phi>0") + else if (cut_kind == cut_type::phigt0) { cut_cell._tdim = 3; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -305,7 +312,7 @@ namespace cutcells::cell::prism throw std::invalid_argument("cutting type unknown"); } - cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity, 9, 6, 44); } template diff --git a/cpp/src/cut_pyramid.cpp b/cpp/src/cut_pyramid.cpp index 3907a93..a9370f9 100644 --- a/cpp/src/cut_pyramid.cpp +++ b/cpp/src/cut_pyramid.cpp @@ -14,15 +14,20 @@ #include "generated/cut_pyramid_outside_tables.h" #include "utils.h" +#include #include #include -#include #include namespace cutcells::cell::pyramid { namespace { + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 28; + constexpr int reserve_connectivity = 48; + constexpr int reserve_types = 48; + // VTK_PYRAMID vertex ordering assumed: // base quad: 0,1,2,3 and apex: 4. // Edge ids must match the VTK TableBasedClip case stream. @@ -37,29 +42,29 @@ namespace cutcells::cell::pyramid + " for flag=" + std::to_string(flag)); } - int lookup_token_or_throw(const std::unordered_map& vertex_case_map, + int lookup_token_or_throw(const VertexCaseMap& vertex_case_map, const int flag, const int token, const char* where) { - const auto it = vertex_case_map.find(token); - if (it == vertex_case_map.end()) + if (token < 0 || token >= static_cast(vertex_case_map.size()) || vertex_case_map[token] < 0) throw_missing_token(flag, token, where); - return it->second; + return vertex_case_map[token]; } template void compute_intersection_points(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { intersection_points.clear(); - vertex_case_map.clear(); + intersection_points.reserve(8 * gdim); + vertex_case_map.fill(-1); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector ip(gdim); + // gdim is 3 for pyramids; use stack arrays to avoid heap allocation per edge + std::array v0 = {}; + std::array v1 = {}; int ip_index = 0; for (int e = 0; e < 8; ++e) @@ -79,10 +84,11 @@ namespace cutcells::cell::pyramid const T ls0 = ls_values[v0_id]; const T ls1 = ls_values[v1_id]; - interval::compute_intersection_point(0.0, v0, v1, ls0, ls1, ip); - - for (int j = 0; j < gdim; ++j) - intersection_points.push_back(ip[j]); + const int ip_offset = static_cast(intersection_points.size()); + intersection_points.resize(intersection_points.size() + gdim); + interval::compute_intersection_point(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip_offset); vertex_case_map[e] = ip_index; ++ip_index; @@ -93,9 +99,9 @@ namespace cutcells::cell::pyramid void ensure_vertex_token(const std::span vertex_coordinates, const int gdim, const int token, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; const int vid = token - 100; @@ -113,9 +119,9 @@ namespace cutcells::cell::pyramid const int* special_point_offset, const int* special_point_data, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { - if (vertex_case_map.find(token) != vertex_case_map.end()) + if (vertex_case_map[token] >= 0) return; if (special_point_count == nullptr || special_point_offset == nullptr || special_point_data == nullptr) @@ -137,7 +143,8 @@ namespace cutcells::cell::pyramid if (nrefs <= 0) throw std::runtime_error("Malformed special point definition"); - std::vector coord(gdim, T(0)); + // gdim is at most 3 for 3-D cells; use stack storage + std::array coord = {T(0), T(0), T(0)}; for (int r = 0; r < nrefs; ++r) { const int ref = special_point_data[idx++]; @@ -188,7 +195,7 @@ namespace cutcells::cell::pyramid const int* special_point_data, bool triangulate, CutCell& cut_cell, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { const int cell_begin = case_offsets[flag]; const int cell_end = case_offsets[flag + 1]; @@ -198,8 +205,8 @@ namespace cutcells::cell::pyramid const type sub_type = cell_types[cell_idx]; const int* verts = subcell_verts[cell_idx]; - std::vector verts_local; - verts_local.reserve(MaxVerts); + std::array verts_local; + int nverts = 0; for (int i = 0; i < MaxVerts; ++i) { const int token = verts[i]; @@ -221,20 +228,19 @@ namespace cutcells::cell::pyramid cut_cell, vertex_case_map); } - verts_local.push_back(lookup_token_or_throw(vertex_case_map, flag, token, "decode_case")); + verts_local[nverts++] = lookup_token_or_throw(vertex_case_map, flag, token, "decode_case"); } - if (triangulate && sub_type == type::quadrilateral && verts_local.size() == 4) + if (triangulate && sub_type == type::quadrilateral && nverts == 4) { - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[1], verts_local[2]}); - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[2], verts_local[3]}); + const std::array t0 = {verts_local[0], verts_local[1], verts_local[2]}; + const std::array t1 = {verts_local[0], verts_local[2], verts_local[3]}; + cutcells::cell::append_cell(cut_cell, type::triangle, t0, 3); + cutcells::cell::append_cell(cut_cell, type::triangle, t1, 3); } else { - cut_cell._types.push_back(sub_type); - cut_cell._connectivity.push_back(std::move(verts_local)); + cutcells::cell::append_cell(cut_cell, sub_type, verts_local.data(), nverts); } } } @@ -267,17 +273,19 @@ namespace cutcells::cell::pyramid } // Compute intersections (shared for all parts) - std::vector intersection_points; - std::unordered_map vertex_case_map; + thread_local std::vector intersection_points; + VertexCaseMap vertex_case_map; compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_lt0, intersection_points, vertex_case_map); cut_cell._gdim = gdim; - cut_cell._vertex_coords = std::move(intersection_points); - cut_cell._types.clear(); - cut_cell._connectivity.clear(); + cut_cell._vertex_coords.assign(intersection_points.begin(), intersection_points.end()); + cutcells::cell::clear_cell_topology(cut_cell); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); + const cut_type cut_kind = string_to_cut_type(cut_type_str); - if (cut_type_str == "phi=0") + if (cut_kind == cut_type::phieq0) { cut_cell._tdim = 2; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -285,7 +293,7 @@ namespace cutcells::cell::pyramid nullptr, nullptr, nullptr, triangulate, cut_cell, vertex_case_map); } - else if (cut_type_str == "phi<0") + else if (cut_kind == cut_type::philt0) { cut_cell._tdim = 3; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -293,7 +301,7 @@ namespace cutcells::cell::pyramid special_point_count_inside, special_point_offset_inside, special_point_data_inside, triangulate, cut_cell, vertex_case_map); } - else if (cut_type_str == "phi>0") + else if (cut_kind == cut_type::phigt0) { cut_cell._tdim = 3; decode_case(vertex_coordinates, gdim, flag_lt0, @@ -306,7 +314,7 @@ namespace cutcells::cell::pyramid throw std::invalid_argument("cutting type unknown"); } - cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity, 8, 5, 1); } template diff --git a/cpp/src/cut_quadrilateral.cpp b/cpp/src/cut_quadrilateral.cpp index 9a720a3..943b49c 100644 --- a/cpp/src/cut_quadrilateral.cpp +++ b/cpp/src/cut_quadrilateral.cpp @@ -18,12 +18,16 @@ #include #include #include -#include namespace cutcells::cell::quadrilateral { namespace { + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 12; + constexpr int reserve_connectivity = 16; + constexpr int reserve_types = 16; + inline bool case_is_ambiguous(int flag) { // Opposite-corner patterns 0b0101 (5) and 0b1010 (10) @@ -44,16 +48,17 @@ namespace cutcells::cell::quadrilateral void compute_intersection_points(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { const auto& intersected = intersected_edges[flag]; intersection_points.clear(); - vertex_case_map.clear(); + intersection_points.reserve(4 * gdim); + vertex_case_map.fill(-1); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector ip(gdim); + // gdim is at most 3; use stack arrays to avoid heap allocations + std::array v0 = {}; + std::array v1 = {}; int ip_index = 0; for (int e = 0; e < 4; ++e) @@ -73,12 +78,11 @@ namespace cutcells::cell::quadrilateral const T ls0 = ls_values[v0_id]; const T ls1 = ls_values[v1_id]; - interval::compute_intersection_point(0.0, v0, v1, ls0, ls1, ip); - - for (int j = 0; j < gdim; ++j) - { - intersection_points.push_back(ip[j]); - } + const int ip_offset = static_cast(intersection_points.size()); + intersection_points.resize(intersection_points.size() + gdim); + interval::compute_intersection_point(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip_offset); vertex_case_map[e] = ip_index; ++ip_index; @@ -91,23 +95,23 @@ namespace cutcells::cell::quadrilateral const int (*subcell_verts)[MaxVerts], const std::span intersection_points, bool triangulate, CutCell& cut_cell, - const std::unordered_map& edge_ip_map, - std::unordered_map& token_to_local_out) + const VertexCaseMap& edge_ip_map, + VertexCaseMap& vertex_case_map_out) { // Map from token (edge id or 100+vid) to local vertex index in cut_cell._vertex_coords - std::unordered_map token_to_local; + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); // Prefill edge intersections so they get stable indices for (int e = 0; e < 4; ++e) { - const auto it = edge_ip_map.find(e); - if (it == edge_ip_map.end()) + const int ip_idx = edge_ip_map[e]; + if (ip_idx < 0) continue; - const int token = it->first; // edge id - const int ip_idx = it->second; + const int token = e; // edge id const int local_idx = static_cast(cut_cell._vertex_coords.size() / gdim); - token_to_local[token] = local_idx; + vertex_case_map[token] = local_idx; cut_cell._vertex_coords.resize(cut_cell._vertex_coords.size() + gdim); for (int j = 0; j < gdim; ++j) cut_cell._vertex_coords[local_idx * gdim + j] = intersection_points[ip_idx * gdim + j]; @@ -115,18 +119,21 @@ namespace cutcells::cell::quadrilateral auto get_local = [&](int token) -> int { - auto it = token_to_local.find(token); - if (it != token_to_local.end()) - return it->second; + if (token < 0 || token >= static_cast(vertex_case_map.size())) + throw std::runtime_error("quadrilateral::decode_range token out of bounds"); + if (vertex_case_map[token] >= 0) + return vertex_case_map[token]; const int local_idx = static_cast(cut_cell._vertex_coords.size() / gdim); - token_to_local[token] = local_idx; + vertex_case_map[token] = local_idx; cut_cell._vertex_coords.resize(cut_cell._vertex_coords.size() + gdim); if (token < 100) { // edge intersection point - const int ip_idx = edge_ip_map.at(token); + const int ip_idx = edge_ip_map[token]; + if (ip_idx < 0) + throw std::runtime_error("quadrilateral::decode_range missing edge token"); for (int j = 0; j < gdim; ++j) cut_cell._vertex_coords[local_idx * gdim + j] = intersection_points[ip_idx * gdim + j]; } @@ -144,31 +151,31 @@ namespace cutcells::cell::quadrilateral const type sub_type = cell_types[cell_idx]; const int* verts = subcell_verts[cell_idx]; - std::vector verts_local; + std::array verts_local; + int nverts = 0; for (int i = 0; i < MaxVerts; ++i) { const int token = verts[i]; if (token == -1) break; - verts_local.push_back(get_local(token)); + verts_local[nverts++] = get_local(token); } - if (triangulate && sub_type == type::quadrilateral && verts_local.size() == 4) + if (triangulate && sub_type == type::quadrilateral && nverts == 4) { // split into two triangles (0,1,2) and (0,2,3) - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[1], verts_local[2]}); - cut_cell._types.push_back(type::triangle); - cut_cell._connectivity.push_back({verts_local[0], verts_local[2], verts_local[3]}); + const std::array t0 = {verts_local[0], verts_local[1], verts_local[2]}; + const std::array t1 = {verts_local[0], verts_local[2], verts_local[3]}; + cutcells::cell::append_cell(cut_cell, type::triangle, t0, 3); + cutcells::cell::append_cell(cut_cell, type::triangle, t1, 3); } else { - cut_cell._types.push_back(sub_type); - cut_cell._connectivity.push_back(std::move(verts_local)); + cutcells::cell::append_cell(cut_cell, sub_type, verts_local.data(), nverts); } } - token_to_local_out = std::move(token_to_local); + vertex_case_map_out = std::move(vertex_case_map); } template @@ -177,13 +184,13 @@ namespace cutcells::cell::quadrilateral const int (*subcell_verts)[MaxVerts], const std::span intersection_points, bool triangulate, CutCell& cut_cell, - const std::unordered_map& edge_ip_map, - std::unordered_map& token_to_local_out) + const VertexCaseMap& edge_ip_map, + VertexCaseMap& vertex_case_map_out) { decode_range(vertex_coordinates, gdim, case_offsets[flag], case_offsets[flag + 1], cell_types, subcell_verts, - intersection_points, triangulate, cut_cell, edge_ip_map, token_to_local_out); + intersection_points, triangulate, cut_cell, edge_ip_map, vertex_case_map_out); } } // namespace @@ -213,22 +220,25 @@ namespace cutcells::cell::quadrilateral } // Compute intersections (shared for all parts) - std::vector intersection_points; - std::unordered_map edge_ip_map; + thread_local std::vector intersection_points; + VertexCaseMap edge_ip_map; compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_interior, intersection_points, edge_ip_map); // Final token->local vertex index map used to populate CutCell::_vertex_parent_entity - std::unordered_map token_to_local; + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); cut_cell._gdim = gdim; cut_cell._vertex_coords.clear(); - cut_cell._types.clear(); - cut_cell._connectivity.clear(); + cutcells::cell::clear_cell_topology(cut_cell); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); const bool is_amb = case_is_ambiguous(flag_interior); const int variant = asymptotic_decider(ls_values[0], ls_values[1], ls_values[2], ls_values[3]) ? 0 : 1; + const cut_type cut_kind = string_to_cut_type(cut_type_str); - if (cut_type_str == "phi=0") + if (cut_kind == cut_type::phieq0) { cut_cell._tdim = 1; std::span ip_span(intersection_points.data(), intersection_points.size()); @@ -239,16 +249,16 @@ namespace cutcells::cell::quadrilateral const int end = amb_range_interface[4 * amb_id + 2 * variant + 1]; decode_range(vertex_coordinates, gdim, begin, end, subcell_type_interface, subcell_verts_interface, - ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } else { decode_case(vertex_coordinates, gdim, flag_interior, case_subcell_offset_interface, subcell_type_interface, - subcell_verts_interface, ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + subcell_verts_interface, ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } } - else if (cut_type_str == "phi<0") + else if (cut_kind == cut_type::philt0) { cut_cell._tdim = 2; std::span ip_span(intersection_points.data(), intersection_points.size()); @@ -259,16 +269,16 @@ namespace cutcells::cell::quadrilateral const int end = amb_range_inside[4 * amb_id + 2 * variant + 1]; decode_range(vertex_coordinates, gdim, begin, end, subcell_type_inside, subcell_verts_inside, - ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } else { decode_case(vertex_coordinates, gdim, flag_interior, case_subcell_offset_inside, subcell_type_inside, - subcell_verts_inside, ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + subcell_verts_inside, ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } } - else if (cut_type_str == "phi>0") + else if (cut_kind == cut_type::phigt0) { cut_cell._tdim = 2; std::span ip_span(intersection_points.data(), intersection_points.size()); @@ -281,13 +291,13 @@ namespace cutcells::cell::quadrilateral const int end = amb_range_outside[4 * amb_id + 2 * variant + 1]; decode_range(vertex_coordinates, gdim, begin, end, subcell_type_outside, subcell_verts_outside, - ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } else { decode_case(vertex_coordinates, gdim, flag_interior, case_subcell_offset_outside, subcell_type_outside, - subcell_verts_outside, ip_span, triangulate, cut_cell, edge_ip_map, token_to_local); + subcell_verts_outside, ip_span, triangulate, cut_cell, edge_ip_map, vertex_case_map); } } else @@ -295,7 +305,7 @@ namespace cutcells::cell::quadrilateral throw std::invalid_argument("cutting type unknown"); } - cutcells::utils::create_vertex_parent_entity_map(token_to_local, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity, 4, 4); } template diff --git a/cpp/src/cut_tetrahedron.cpp b/cpp/src/cut_tetrahedron.cpp index 819715a..f812dad 100644 --- a/cpp/src/cut_tetrahedron.cpp +++ b/cpp/src/cut_tetrahedron.cpp @@ -7,6 +7,8 @@ #include "cut_tetrahedron.h" #include "cut_interval.h" #include "cell_flags.h" +#include "reference_cell.h" +#include "prism_midpoint_split.h" #include "triangulation.h" #include "span_math.h" #include "utils.h" @@ -14,7 +16,7 @@ #include #include #include -#include +#include #include namespace cutcells::cell @@ -22,6 +24,11 @@ namespace cutcells::cell // Look up tables for intersection namespace{ + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 16; + constexpr int reserve_connectivity = 16; + constexpr int reserve_types = 16; + // Choose numbering of edges for cutting in accordance with numbering in vtk // Tetrahedron numbering: // @@ -102,18 +109,18 @@ namespace{ { -1, -1, -1, -1, -1, -1 }, // 0 { 0, 3, 2, 100, -1, -1 }, // 1 { 0, 1, 4, 101, -1, -1 }, // 2 - { 101, 1, 4, 100, 2, 3 }, // 3 + { 100, 2, 3, 101, 1, 4 }, // 3 (prism: swapped bottom/top) { 1, 2, 5, 102, -1, -1 }, // 4 - { 102, 5, 1, 100, 3, 0 }, // 5 - { 102, 2, 5, 101, 0, 4 }, // 6 - { 3, 4, 5, 100, 101, 102}, // 7 + { 100, 3, 0, 102, 5, 1 }, // 5 (prism: swapped bottom/top) + { 101, 0, 4, 102, 2, 5 }, // 6 (prism: swapped bottom/top) + { 100, 101, 102, 3, 4, 5}, // 7 (prism: swapped bottom/top) { 3, 4, 5, 103, -1, -1 }, // 8 - { 103, 4, 5, 100, 0, 2 }, // 9 - { 103, 5, 3, 101, 1, 0 }, // 10 - { 100, 101, 103, 2, 1, 5}, // 11 - { 2, 102, 1, 3, 103, 4 }, // 12 - { 0, 1, 4, 100, 102, 103}, // 13 - { 0, 3, 2, 101, 103, 102}, // 14 + { 100, 0, 2, 103, 4, 5 }, // 9 (prism: swapped bottom/top) + { 101, 1, 0, 103, 5, 3 }, // 10 (prism: swapped bottom/top) + { 2, 1, 5, 100, 101, 103}, // 11 (prism: swapped bottom/top) + { 3, 103, 4, 2, 102, 1 }, // 12 (prism: swapped bottom/top) + { 100, 102, 103, 4, 0, 1}, // 13 (prism: swapped bottom/top) + { 101, 103, 102, 0, 3, 2}, // 14 (prism: swapped bottom/top) { 100, 101, 102, 103, -1, -1} // 15 }; @@ -154,7 +161,16 @@ namespace tetrahedron{ if(cell_type == type::prism && triangulate == true) { - num_sub_elements = 3; + int num_roots = 0; + for (int i = 0; i < 6; ++i) + num_roots += tetrahedron_sub_element[flag][i] < 100 ? 1 : 0; + + if (num_roots == 3) + num_sub_elements = 7; + else if (num_roots == 4) + num_sub_elements = 4; + else + throw std::runtime_error("tetrahedron::get_num_sub_elements invalid prism family"); } return num_sub_elements; } @@ -174,7 +190,7 @@ namespace tetrahedron{ template void compute_intersection_points(std::span vertex_coordinates, const int gdim, std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { // vertex ids will be edges[edge_0][0] edges[edge_0][1] // get vertex coordinates and interpolate @@ -182,9 +198,9 @@ namespace tetrahedron{ intersection_points.resize(num_intersection_points*gdim); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector intersection_point(gdim); + // gdim is at most 3; use stack arrays to avoid heap allocation per intersection point + std::array v0 = {}; + std::array v1 = {}; for(int ip=0; ip(0.0, v0, v1,ls0, ls1, intersection_point); - - for(int j=0;j(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip * gdim); } int cnt = 0; @@ -223,7 +236,7 @@ namespace tetrahedron{ template void create_sub_cell_vertex_coords(const int& flag, const std::span vertex_coordinates, const int gdim, const std::span intersection_points, - std::vector& coords, std::unordered_map& vertex_case_map) + std::vector& coords, VertexCaseMap& vertex_case_map) { int num_intersection_points = intersection_points.size()/gdim; type cell_type = tetrahedron_sub_element_cell_types[flag]; @@ -264,132 +277,73 @@ namespace tetrahedron{ } } - void create_interface_cells(const int& flag, std::unordered_map& vertex_case_map, const bool &triangulate, - std::vector>& interface_cells, std::vector& interface_cell_types) + template + void create_cut_cell(const std::span vertex_coordinates, + const int gdim, const std::span ls_values, + const std::string& cut_type_str, + CutCell& cut_cell, bool triangulate, + const std::span intersection_points, + VertexCaseMap& vertex_case_map) { - int num_interface_cells = get_num_interface_elements(flag, triangulate); - interface_cells.resize(num_interface_cells); - interface_cell_types.resize(num_interface_cells); - type sub_cell_type = interface_sub_element_cell_types[flag]; - - // initialize cell types - for(std::size_t i =0; i> triangles; - triangulation(sub_cell_type, tetrahedron_intersected_edges[flag], triangles); + std::array prism_tokens = {}; + for (int j = 0; j < 6; ++j) + prism_tokens[j] = tetrahedron_sub_element[flag][j]; - for(int i=0;i prism_vertex_coords(static_cast(6 * gdim)); + for (int local_id = 0; local_id < 6; ++local_id) { - interface_cells[i].resize(3); + const int token = prism_tokens[static_cast(local_id)]; + const int vertex_id = vertex_case_map[token]; + if (vertex_id < 0) + throw std::runtime_error("tetrahedron::create_cut_cell missing prism vertex token"); - for(int j=0;j<3;j++) + for (int d = 0; d < gdim; ++d) { - interface_cells[i][j] = vertex_case_map[triangles[i][j]]; + prism_vertex_coords[static_cast(local_id * gdim + d)] + = cut_cell._vertex_coords[static_cast(vertex_id * gdim + d)]; } } - } - else - { - for(int i=0;i( + std::span(prism_vertex_coords.data(), prism_vertex_coords.size()), + gdim, + std::span(prism_tokens.data(), prism_tokens.size()), + next_token_base); - void create_sub_cells(const int& flag, const bool &triangulate, std::unordered_map& vertex_case_map, std::vector>& sub_cells, - std::vector& sub_cell_types) - { - // Allocate memory - int num_sub_elements = get_num_sub_elements(flag, triangulate); - type sub_cell_type = tetrahedron_sub_element_cell_types[flag]; - sub_cells.resize(num_sub_elements); - sub_cell_types.resize(num_sub_elements); + const int old_num_vertices = static_cast(cut_cell._vertex_coords.size() / gdim); + cut_cell._vertex_coords.insert( + cut_cell._vertex_coords.end(), + split.added_vertex_coords.begin(), + split.added_vertex_coords.end()); - for(int i=0;i= static_cast(vertex_case_map.size())) + throw std::runtime_error("tetrahedron::create_cut_cell midpoint token out of bounds"); + vertex_case_map[token] = old_num_vertices + static_cast(i); } - else - { - sub_cell_types[i] = tetrahedron_sub_element_cell_types[flag]; - } - } - //collect all vertices of sub-elements (elements are separated by -1 in list) - if(sub_cell_type == type::prism && triangulate == true) - { - std::vector> triangles; - triangulation(sub_cell_type, tetrahedron_sub_element[flag], triangles); - - for(int i=0;i t = { + vertex_case_map[tet_tokens[0]], + vertex_case_map[tet_tokens[1]], + vertex_case_map[tet_tokens[2]], + vertex_case_map[tet_tokens[3]], + }; + cutcells::cell::append_cell(cut_cell, type::tetrahedron, t, 4); } - } - else - { - //number of entries in sub_element table - int ncols = 6; - - for(int i=0;i - void create_cut_cell(const std::span vertex_coordinates, - const int gdim, const std::span ls_values, - const std::string& cut_type_str, - CutCell& cut_cell, bool triangulate, - const std::span intersection_points, - std::unordered_map& vertex_case_map) - { - cut_cell._gdim = gdim; - - if(cut_type_str=="phi=0") + if(cut_kind == cut_type::phieq0) { cut_cell._tdim = 2; int flag_interior = get_entity_flag(ls_values, false); @@ -400,28 +354,76 @@ namespace tetrahedron{ { cut_cell._vertex_coords[i] = intersection_points[i]; } - // Determine interface cells for triangle this is an interval with two points (the intersection points) - create_interface_cells(flag_interior, vertex_case_map, triangulate, cut_cell._connectivity, cut_cell._types); + // Append interface cells directly + type iface_type = interface_sub_element_cell_types[flag_interior]; + if(iface_type == type::quadrilateral && triangulate) + { + std::array iface_tokens; + for (int i = 0; i < 4; ++i) + iface_tokens[i] = tetrahedron_intersected_edges[flag_interior][i]; + cell::reorder_subcell_vertices_from_vtk_to_basix( + iface_type, iface_tokens, 4); + std::vector> triangles; + triangulation(iface_type, iface_tokens.data(), triangles); + for(int i = 0; i < static_cast(triangles.size()); ++i) + { + std::array t; + t[0] = vertex_case_map[triangles[i][0]]; + t[1] = vertex_case_map[triangles[i][1]]; + t[2] = vertex_case_map[triangles[i][2]]; + cutcells::cell::append_cell(cut_cell, type::triangle, t, 3); + } + } + else + { + int num_vertices = get_num_vertices(iface_type); + std::array verts; + for(int j = 0; j < num_vertices; ++j) + verts[j] = vertex_case_map[tetrahedron_intersected_edges[flag_interior][j]]; + cutcells::cell::append_cell(cut_cell, iface_type, verts, num_vertices); + } } - else if(cut_type_str=="phi<0") + else if(cut_kind == cut_type::philt0) { cut_cell._tdim = 3; int flag_interior = get_entity_flag(ls_values, false); create_sub_cell_vertex_coords(flag_interior, vertex_coordinates, gdim, intersection_points, cut_cell._vertex_coords, vertex_case_map); - //Determine interior sub-cells - create_sub_cells(flag_interior, triangulate, vertex_case_map, - cut_cell._connectivity, cut_cell._types); + // Append sub-cells directly + type sub_cell_type = tetrahedron_sub_element_cell_types[flag_interior]; + if(sub_cell_type == type::prism && triangulate) + { + append_midpoint_split_prism(flag_interior); + } + else + { + int num_vertices = get_num_vertices(sub_cell_type); + std::array verts; + for(int j = 0; j < num_vertices; ++j) + verts[j] = vertex_case_map[tetrahedron_sub_element[flag_interior][j]]; + cutcells::cell::append_cell(cut_cell, sub_cell_type, verts, num_vertices); + } } - else if(cut_type_str=="phi>0") + else if(cut_kind == cut_type::phigt0) { cut_cell._tdim = 3; - //Determine exterior sub-cells int flag_exterior = get_entity_flag(ls_values, true); create_sub_cell_vertex_coords(flag_exterior, vertex_coordinates, gdim, intersection_points, cut_cell._vertex_coords, vertex_case_map); - create_sub_cells(flag_exterior, triangulate, vertex_case_map, - cut_cell._connectivity, cut_cell._types); + // Append sub-cells directly + type sub_cell_type = tetrahedron_sub_element_cell_types[flag_exterior]; + if(sub_cell_type == type::prism && triangulate) + { + append_midpoint_split_prism(flag_exterior); + } + else + { + int num_vertices = get_num_vertices(sub_cell_type); + std::array verts; + for(int j = 0; j < num_vertices; ++j) + verts[j] = vertex_case_map[tetrahedron_sub_element[flag_exterior][j]]; + cutcells::cell::append_cell(cut_cell, sub_cell_type, verts, num_vertices); + } } else { @@ -430,22 +432,20 @@ namespace tetrahedron{ } template - void str(CutCell& cut_cell, std::unordered_map& vertex_case_map) + void str(CutCell& cut_cell, const VertexCaseMap& vertex_case_map) { std::cout << "vertex case map=["; - for(auto& it: vertex_case_map) + for(std::size_t token = 0; token < vertex_case_map.size(); ++token) { - std::cout << it.first << ": " << it.second << std::endl; + if (vertex_case_map[token] >= 0) + std::cout << token << ": " << vertex_case_map[token] << std::endl; } std::cout << "]" << std::endl; std::cout << "connectivity=["; for(auto &i: cut_cell._connectivity) { - for(auto &j : i) - { - std::cout << j << ", "; - } + std::cout << i << ", "; } std::cout << "]" << std::endl; @@ -480,28 +480,36 @@ namespace tetrahedron{ // Compute intersection points these are required for any cut cell part (interface, interior, exterior) // get the number of intersection points - std::vector intersection_points; + thread_local std::vector intersection_points; + intersection_points.clear(); + intersection_points.reserve(4 * gdim); // the vertex case map, // first few entries map from intersected edge to intersection point number // next entries map from orginal vertex id to number of vertex in vertex_coordinates of CutCell (renumbered to go from 0,...,N) // example: intersected edges 0 -> 0, 2 -> 1 // then orginal vertex 101 -> 2 , 102 -> 3 etc. - std::unordered_map vertex_case_map; + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_interior, intersection_points, vertex_case_map); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); + create_cut_cell(vertex_coordinates, gdim, ls_values, cut_type_str, cut_cell, triangulate, intersection_points, vertex_case_map); - cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map( + vertex_case_map, cut_cell._vertex_parent_entity, + /*n_edges=*/6, /*n_vertices=*/4); } template T volume(const std::span vertex_coordinates, const int gdim) { - const auto a = vertex_coordinates.subspan(0, gdim); - const auto b = vertex_coordinates.subspan(gdim, gdim); - const auto c = vertex_coordinates.subspan(2*gdim, gdim); - const auto d = vertex_coordinates.subspan(3*gdim, gdim); + const auto a = cutcells::math::to_vec3(vertex_coordinates.subspan(0, gdim)); + const auto b = cutcells::math::to_vec3(vertex_coordinates.subspan(gdim, gdim)); + const auto c = cutcells::math::to_vec3(vertex_coordinates.subspan(2*gdim, gdim)); + const auto d = cutcells::math::to_vec3(vertex_coordinates.subspan(3*gdim, gdim)); const auto ad = cutcells::math::subtract(a,d); const auto bd = cutcells::math::subtract(b,d); @@ -523,14 +531,14 @@ namespace tetrahedron{ template void compute_intersection_points(std::span vertex_coordinates, const int gdim, std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map); + VertexCaseMap& vertex_case_map); template void compute_intersection_points(std::span vertex_coordinates, const int gdim, std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map); + VertexCaseMap& vertex_case_map); template double volume(const std::span vertex_coordinates, const int gdim); template float volume(const std::span vertex_coordinates, const int gdim); //----------------------------------------------------------------------------- } -} \ No newline at end of file +} diff --git a/cpp/src/cut_triangle.cpp b/cpp/src/cut_triangle.cpp index b0d94f5..7956337 100644 --- a/cpp/src/cut_triangle.cpp +++ b/cpp/src/cut_triangle.cpp @@ -7,6 +7,8 @@ #include "cut_triangle.h" #include "cut_interval.h" #include "cell_flags.h" +#include "quad_midpoint_split.h" +#include "reference_cell.h" #include "triangulation.h" #include "span_math.h" #include "utils.h" @@ -14,13 +16,18 @@ #include #include #include -#include +#include #include namespace cutcells::cell { // Look up tables for intersection namespace{ + using VertexCaseMap = std::array; + constexpr int reserve_vertex_coords = 8; + constexpr int reserve_connectivity = 8; + constexpr int reserve_types = 8; + // Choose numbering of edges for cutting in accordance with numbering of vtk int edges[3][2] = {{0,1}, {1,2}, {2,0}}; @@ -88,7 +95,7 @@ namespace triangle{ if(cell_type == type::quadrilateral && triangulate == true) { - num_sub_elements = 2; + num_sub_elements = 3; } return num_sub_elements; } @@ -96,7 +103,7 @@ namespace triangle{ template void compute_intersection_points(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const int flag, std::vector& intersection_points, - std::unordered_map& vertex_case_map) + VertexCaseMap& vertex_case_map) { // vertex ids will be edges[edge_0][0] edges[edge_0][1] // get vertex coordinates and interpolate @@ -104,9 +111,9 @@ namespace triangle{ intersection_points.resize(num_intersection_points*gdim); - std::vector v0(gdim); - std::vector v1(gdim); - std::vector intersection_point(gdim); + // gdim is at most 3; use stack arrays to avoid heap allocation per intersection point + std::array v0 = {}; + std::array v1 = {}; for(int ip=0; ip(0.0, v0, v1,ls0, ls1, intersection_point); - - for(int j=0;j(T(0), std::span(v0.data(), gdim), + std::span(v1.data(), gdim), + ls0, ls1, intersection_points, ip * gdim); } vertex_case_map[triangle_intersected_edges[flag][0]] = 0; @@ -140,7 +144,7 @@ namespace triangle{ template void create_sub_cell_vertex_coords(const int& flag, const std::span vertex_coordinates, const int gdim, const std::span intersection_points, - std::vector& coords, std::unordered_map& vertex_case_map) + std::vector& coords, VertexCaseMap& vertex_case_map) { int num_intersection_points = intersection_points.size()/gdim; type cell_type = triangle_sub_element_cell_types[flag]; @@ -181,92 +185,72 @@ namespace triangle{ } } - void create_interface_cells(std::vector>& interface_cells, std::vector& interface_cell_types) - { - interface_cells.resize(1); - interface_cells[0].resize(2); - interface_cells[0][0] = 0; - interface_cells[0][1] = 1; - - interface_cell_types.resize(1); - interface_cell_types[0] = type::interval; - } - - void create_sub_cells(const int& flag, bool triangulate, std::vector>& sub_cells, - std::vector& sub_cell_types, std::unordered_map& vertex_case_map) + template + void create_cut_cell(const std::span vertex_coordinates, + const int gdim, const std::span ls_values, + const std::string& cut_type_str, + CutCell& cut_cell, bool triangulate, + const std::span intersection_points, + VertexCaseMap& vertex_case_map) { - // Allocate memory - int num_sub_elements = get_num_sub_elements(flag, triangulate); - sub_cells.resize(num_sub_elements); - sub_cell_types.resize(num_sub_elements); + cut_cell._gdim = gdim; + cutcells::cell::clear_cell_topology(cut_cell); + const cut_type cut_kind = string_to_cut_type(cut_type_str); - for(int i=0;i quad_tokens = {}; + for (int j = 0; j < 4; ++j) + quad_tokens[j] = triangle_sub_element[flag][j]; - if(triangulate) + std::vector quad_vertex_coords(static_cast(4 * gdim)); + for (int local_id = 0; local_id < 4; ++local_id) { - sub_cell_type = type::triangle; - } - else - { - sub_cell_type = triangle_sub_element_cell_types[flag]; - } - sub_cell_types[i] = sub_cell_type; - int num_vertices = get_num_vertices(sub_cell_type); - sub_cells[i].resize(num_vertices); - } - - // Fill in vertex ids - //int vertices[4] = triangle_sub_element[flag]; - type sub_cell_type = triangle_sub_element_cell_types[flag]; - - //collect all vertices of sub-elements (elements are separated by -1 in list) - if(sub_cell_type == type::quadrilateral && triangulate == true) - { - std::vector> triangles; - triangulation(sub_cell_type, triangle_sub_element[flag], triangles); + const int token = quad_tokens[static_cast(local_id)]; + const int vertex_id = vertex_case_map[token]; + if (vertex_id < 0) + throw std::runtime_error("triangle::create_cut_cell missing quadrilateral vertex token"); - for(int i=0;i(local_id * gdim + d)] + = cut_cell._vertex_coords[static_cast(vertex_id * gdim + d)]; } - } - else - { - //number of entries in sub_element table - int ncols = 4; - int sub_element = 0; + } - for(int i=0;i( + std::span(quad_vertex_coords.data(), quad_vertex_coords.size()), + gdim, + std::span(quad_tokens.data(), quad_tokens.size()), + next_token_base); + + const int old_num_vertices = static_cast(cut_cell._vertex_coords.size() / gdim); + cut_cell._vertex_coords.insert( + cut_cell._vertex_coords.end(), + split.added_vertex_coords.begin(), + split.added_vertex_coords.end()); + + for (std::size_t i = 0; i < split.added_vertex_tokens.size(); ++i) { - for(int j=0;j= static_cast(vertex_case_map.size())) + throw std::runtime_error("triangle::create_cut_cell midpoint token out of bounds"); + vertex_case_map[token] = old_num_vertices + static_cast(i); } - } - } - template - void create_cut_cell(const std::span vertex_coordinates, - const int gdim, const std::span ls_values, - const std::string& cut_type_str, - CutCell& cut_cell, bool triangulate, - const std::span intersection_points, - std::unordered_map& vertex_case_map) - { - cut_cell._gdim = gdim; + for (const auto& tri_tokens : split.triangles) + { + std::array t = { + vertex_case_map[tri_tokens[0]], + vertex_case_map[tri_tokens[1]], + vertex_case_map[tri_tokens[2]], + }; + cutcells::cell::append_cell(cut_cell, type::triangle, t, 3); + } + }; - if(cut_type_str=="phi=0") + if(cut_kind == cut_type::phieq0) { cut_cell._tdim = 1; int flag_interior = get_entity_flag(ls_values, false); @@ -277,28 +261,51 @@ namespace triangle{ { cut_cell._vertex_coords[i] = intersection_points[i]; } - // Determine interface cells for triangle this is an interval with two points (the intersection points) - create_interface_cells(cut_cell._connectivity, cut_cell._types); + // Interface is a single interval spanning the two intersection points + const std::array iface_verts = {0, 1}; + cutcells::cell::append_cell(cut_cell, type::interval, iface_verts, 2); } - else if(cut_type_str=="phi<0") + else if(cut_kind == cut_type::philt0) { cut_cell._tdim = 2; int flag_interior = get_entity_flag(ls_values, false); create_sub_cell_vertex_coords(flag_interior, vertex_coordinates, gdim, intersection_points, cut_cell._vertex_coords, vertex_case_map); - //Determine interior sub-cells - create_sub_cells(flag_interior, triangulate, cut_cell._connectivity, - cut_cell._types, vertex_case_map); + // Append sub-cells directly into flat storage without staging + type sub_cell_type = triangle_sub_element_cell_types[flag_interior]; + if(sub_cell_type == type::quadrilateral && triangulate) + { + append_midpoint_split_quad(flag_interior); + } + else + { + int num_vertices = get_num_vertices(sub_cell_type); + std::array verts; + for(int j = 0; j < num_vertices; ++j) + verts[j] = vertex_case_map[triangle_sub_element[flag_interior][j]]; + cutcells::cell::append_cell(cut_cell, sub_cell_type, verts, num_vertices); + } } - else if(cut_type_str=="phi>0") + else if(cut_kind == cut_type::phigt0) { cut_cell._tdim = 2; - //Determine exterior sub-cells int flag_exterior = get_entity_flag(ls_values, true); create_sub_cell_vertex_coords(flag_exterior, vertex_coordinates, gdim, intersection_points, - cut_cell._vertex_coords, vertex_case_map); - create_sub_cells(flag_exterior, triangulate, cut_cell._connectivity, - cut_cell._types, vertex_case_map); + cut_cell._vertex_coords, vertex_case_map); + // Append sub-cells directly into flat storage without staging + type sub_cell_type = triangle_sub_element_cell_types[flag_exterior]; + if(sub_cell_type == type::quadrilateral && triangulate) + { + append_midpoint_split_quad(flag_exterior); + } + else + { + int num_vertices = get_num_vertices(sub_cell_type); + std::array verts; + for(int j = 0; j < num_vertices; ++j) + verts[j] = vertex_case_map[triangle_sub_element[flag_exterior][j]]; + cutcells::cell::append_cell(cut_cell, sub_cell_type, verts, num_vertices); + } } else { @@ -306,22 +313,20 @@ namespace triangle{ } } template - void str(CutCell& cut_cell, std::unordered_map& vertex_case_map) + void str(CutCell& cut_cell, const VertexCaseMap& vertex_case_map) { std::cout << "vertex case map=["; - for(auto& it: vertex_case_map) + for(std::size_t token = 0; token < vertex_case_map.size(); ++token) { - std::cout << it.first << ": " << it.second << std::endl; + if (vertex_case_map[token] >= 0) + std::cout << token << ": " << vertex_case_map[token] << std::endl; } std::cout << "]" << std::endl; std::cout << "connectivity=["; for(auto &i: cut_cell._connectivity) { - for(auto &j : i) - { - std::cout << j << ", "; - } + std::cout << i << ", "; } std::cout << "]" << std::endl; @@ -356,19 +361,27 @@ namespace triangle{ // Compute intersection points these are required for any cut cell part (interface, interior, exterior) // get the number of intersection points - std::vector intersection_points; + thread_local std::vector intersection_points; + intersection_points.clear(); + intersection_points.reserve(2 * gdim); // the vertex case map, // first few entries map from intersected edge to intersection point number // next entries map from orginal vertex id to number of vertex in vertex_coordinates of CutCell (renumbered to go from 0,...,N) // example: intersected edges 0 -> 0, 2 -> 1 // then orginal vertex 101 -> 2 , 102 -> 3 etc. - std::unordered_map vertex_case_map; + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_interior, intersection_points, vertex_case_map); + cut_cell._vertex_coords.reserve(reserve_vertex_coords * gdim); + cutcells::cell::reserve_cell_topology(cut_cell, reserve_connectivity, reserve_types); + create_cut_cell(vertex_coordinates, gdim, ls_values, cut_type_str, cut_cell, triangulate, intersection_points, vertex_case_map); - cutcells::utils::create_vertex_parent_entity_map(vertex_case_map, cut_cell._vertex_parent_entity); + cutcells::utils::create_vertex_parent_entity_map( + vertex_case_map, cut_cell._vertex_parent_entity, + /*n_edges=*/3, /*n_vertices=*/3); }; // cut triangle version with vector of string in case multiple parts of the cut-cell are needed (very common) @@ -387,8 +400,11 @@ namespace triangle{ // Compute intersection points these are required for any cut cell part (interface, interior, exterior) // get the number of intersection points - std::vector intersection_points; - std::unordered_map vertex_case_map; + thread_local std::vector intersection_points; + intersection_points.clear(); + intersection_points.reserve(2 * gdim); + VertexCaseMap vertex_case_map; + vertex_case_map.fill(-1); compute_intersection_points(vertex_coordinates, gdim, ls_values, flag_interior, intersection_points, vertex_case_map); cut_cell.resize(cut_type_str.size()); @@ -397,6 +413,9 @@ namespace triangle{ { create_cut_cell(vertex_coordinates, gdim, ls_values, cut_type_str[i], cut_cell[i], triangulate, intersection_points, vertex_case_map); + cutcells::utils::create_vertex_parent_entity_map( + vertex_case_map, cut_cell[i]._vertex_parent_entity, + /*n_edges=*/3, /*n_vertices=*/3); } }; @@ -424,10 +443,16 @@ namespace triangle{ template void cut(const std::span vertex_coordinates, const int gdim, const std::span ls_values, const std::string& cut_type_str, CutCell& cut_cell, bool triangulate); + template void cut(const std::span vertex_coordinates, const int gdim, + const std::span ls_values, const std::vector& cut_type_str, + std::vector>& cut_cell, bool triangulate); + template void cut(const std::span vertex_coordinates, const int gdim, + const std::span ls_values, const std::vector& cut_type_str, + std::vector>& cut_cell, bool triangulate); template double volume(const std::span vertex_coordinates, const int gdim); template float volume(const std::span vertex_coordinates, const int gdim); //----------------------------------------------------------------------------- } -} \ No newline at end of file +} diff --git a/cpp/src/edge_certification.cpp b/cpp/src/edge_certification.cpp new file mode 100644 index 0000000..0389539 --- /dev/null +++ b/cpp/src/edge_certification.cpp @@ -0,0 +1,1030 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "edge_certification.h" +#include "bernstein.h" +#include "cell_topology.h" + +#include +#include +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +template +T integer_pow(T base, int exp) +{ + T result = T(1); + for (int i = 0; i < exp; ++i) + result *= base; + return result; +} + +template +T binomial(int n, int k) +{ + if (k < 0 || k > n) + return T(0); + if (k == 0 || k == n) + return T(1); + if (k > n - k) + k = n - k; + + T result = T(1); + for (int i = 0; i < k; ++i) + { + result *= T(n - i); + result /= T(i + 1); + } + return result; +} + +template +T multinomial(int n, const int* alpha, int num_components) +{ + T result = T(1); + int remaining = n; + for (int c = 1; c < num_components; ++c) + { + result *= binomial(remaining, alpha[c]); + remaining -= alpha[c]; + } + return result; +} + +template +std::vector linear_power_poly(T a, T b, int n) +{ + std::vector out(static_cast(n + 1), T(0)); + for (int i = 0; i <= n; ++i) + { + out[static_cast(i)] = + binomial(n, i) * integer_pow(a, n - i) * integer_pow(b, i); + } + return out; +} + +template +std::vector multiply_poly(std::span a, std::span b) +{ + std::vector out(a.size() + b.size() - 1, T(0)); + for (std::size_t i = 0; i < a.size(); ++i) + for (std::size_t j = 0; j < b.size(); ++j) + out[i + j] += a[i] * b[j]; + return out; +} + +template +void add_scaled_poly(std::vector& dst, std::span src, T scale) +{ + if (dst.size() < src.size()) + dst.resize(src.size(), T(0)); + for (std::size_t i = 0; i < src.size(); ++i) + dst[i] += scale * src[i]; +} + +template +void power_to_bernstein_1d(std::span power_coeffs, + std::vector& bernstein_coeffs) +{ + const int degree = static_cast(power_coeffs.size()) - 1; + bernstein_coeffs.assign(static_cast(degree + 1), T(0)); + + for (int i = 0; i <= degree; ++i) + { + T coeff = T(0); + for (int m = 0; m <= i; ++m) + { + coeff += power_coeffs[static_cast(m)] + * binomial(i, m) + / binomial(degree, m); + } + bernstein_coeffs[static_cast(i)] = coeff; + } +} + +// ===================================================================== +// Simplex multi-index helpers (same ordering as bernstein.cpp) +// ===================================================================== + +inline int simplex_index_1d(int i, int /*n*/) { return i; } + +inline int simplex_index_2d(int i, int j, int n) +{ + return j * (n + 1) - j * (j - 1) / 2 + i; +} + +inline int simplex_index_3d(int i, int j, int k, int n) +{ + int offset = 0; + for (int kk = 0; kk < k; ++kk) + { + int m = n - kk; + offset += (m + 1) * (m + 2) / 2; + } + return offset + simplex_index_2d(i, j, n - k); +} + +// ===================================================================== +// Tensor-product index helpers +// ===================================================================== + +inline int tp_index_2d(int ix, int iy, int n) +{ + return iy * (n + 1) + ix; +} + +inline int tp_index_3d(int ix, int iy, int iz, int n) +{ + return iz * (n + 1) * (n + 1) + iy * (n + 1) + ix; +} + +// ===================================================================== +// Edge-to-multi-index mapping for simplex parent edge extraction +// ===================================================================== + +/// For a triangle edge with local vertices (v0, v1) in the canonical numbering +/// {0, 1, 2}, the Bernstein multi-index alpha = (alpha_0, alpha_1, alpha_2) +/// with sum = degree, varies only along the edge. +/// We parameterize: alpha[v0] = n-k, alpha[v1] = k, all others = 0. +/// +/// Returns the linear Bernstein index for the k-th point on that edge. +inline int triangle_edge_coeff_index(int v0, int v1, int k, int n) +{ + // multi-index: alpha[v0] = n-k, alpha[v1] = k, alpha[other] = 0 + int alpha[3] = {0, 0, 0}; + alpha[v0] = n - k; + alpha[v1] = k; + // simplex_index_2d expects (i, j, n) where alpha = (n-i-j, i, j) + return simplex_index_2d(alpha[1], alpha[2], n); +} + +/// Same logic for tetrahedron. +inline int tetrahedron_edge_coeff_index(int v0, int v1, int k, int n) +{ + int alpha[4] = {0, 0, 0, 0}; + alpha[v0] = n - k; + alpha[v1] = k; + // simplex_index_3d expects (i, j, k_idx, n) where alpha = (n-i-j-k_idx, i, j, k_idx) + return simplex_index_3d(alpha[1], alpha[2], alpha[3], n); +} + +} // anonymous namespace + +// ===================================================================== +// subdivide_bernstein_1d +// ===================================================================== + +template +void subdivide_bernstein_1d(std::span coeffs, + T t_split, + std::vector& left, + std::vector& right) +{ + const int p = static_cast(coeffs.size()) - 1; + assert(p >= 0); + + left.resize(static_cast(p + 1)); + right.resize(static_cast(p + 1)); + + // Work array: de Casteljau triangle columns. + // Starting column = coeffs. + std::vector work(coeffs.begin(), coeffs.end()); + + // The left child's i-th coeff is work[0] after i reduction steps. + // The right child's i-th coeff is work[p-i] after (p-i) reduction steps. + left[0] = work[0]; + right[static_cast(p)] = work[static_cast(p)]; + + for (int level = 1; level <= p; ++level) + { + // Reduce: work[j] = (1-t)*work[j] + t*work[j+1] for j = 0..p-level + for (int j = 0; j <= p - level; ++j) + { + work[static_cast(j)] = + (T(1) - t_split) * work[static_cast(j)] + + t_split * work[static_cast(j + 1)]; + } + left[static_cast(level)] = work[0]; + right[static_cast(p - level)] = + work[static_cast(p - level)]; + } +} + +// ===================================================================== +// Sign-hull helpers +// ===================================================================== + +template +bool bernstein_all_positive(std::span coeffs, T tol) +{ + for (const auto& c : coeffs) + if (c <= tol) return false; + return true; +} + +template +bool bernstein_all_negative(std::span coeffs, T tol) +{ + for (const auto& c : coeffs) + if (c >= -tol) return false; + return true; +} + +template +bool bernstein_all_zero(std::span coeffs, T tol) +{ + for (const auto& c : coeffs) + if (std::fabs(c) > tol) return false; + return true; +} + +// ===================================================================== +// find_root_intervals_1d +// ===================================================================== + +template +void find_root_intervals_1d(std::span coeffs, + T t0, T t1, + T zero_tol, T sign_tol, + int depth, int max_depth, + std::vector>& intervals, + bool& has_zero_segment) +{ + // If convex hull excludes zero → no root in this interval. + if (bernstein_all_positive(coeffs, sign_tol) + || bernstein_all_negative(coeffs, sign_tol)) + return; + + // If all coefficients are zero → zero segment. + if (bernstein_all_zero(coeffs, zero_tol)) + { + has_zero_segment = true; + return; + } + + // If max depth reached, record this interval as containing a root. + if (depth >= max_depth) + { + intervals.push_back({t0, t1}); + return; + } + + // Subdivide at midpoint. + T t_mid = (t0 + t1) * T(0.5); + std::vector left_c, right_c; + subdivide_bernstein_1d(coeffs, T(0.5), left_c, right_c); + + find_root_intervals_1d( + std::span(left_c), t0, t_mid, + zero_tol, sign_tol, depth + 1, max_depth, intervals, has_zero_segment); + + find_root_intervals_1d( + std::span(right_c), t_mid, t1, + zero_tol, sign_tol, depth + 1, max_depth, intervals, has_zero_segment); +} + +// ===================================================================== +// Merge overlapping root intervals +// ===================================================================== + +namespace +{ + +template +void merge_root_intervals(std::vector>& intervals, + T merge_tol) +{ + if (intervals.size() <= 1) return; + + // Sort by t0. + std::sort(intervals.begin(), intervals.end(), + [](const EdgeRootInterval& a, const EdgeRootInterval& b) + { return a.t0 < b.t0; }); + + std::vector> merged; + merged.push_back(intervals[0]); + + for (std::size_t i = 1; i < intervals.size(); ++i) + { + auto& back = merged.back(); + if (intervals[i].t0 <= back.t1 + merge_tol) + { + // Overlapping or adjacent — extend. + back.t1 = std::max(back.t1, intervals[i].t1); + } + else + { + merged.push_back(intervals[i]); + } + } + + intervals = std::move(merged); +} + +template +bool linear_one_root_parameter_from_endpoint_values(T phi0, + T phi1, + T zero_tol, + T& root_t) +{ + const bool left_zero = std::fabs(phi0) <= zero_tol; + const bool right_zero = std::fabs(phi1) <= zero_tol; + + root_t = T(0); + if (left_zero && right_zero) + return false; + if (left_zero) + return true; + if (right_zero) + { + root_t = T(1); + return true; + } + + const bool brackets_zero = (phi0 < T(0) && phi1 > T(0)) + || (phi0 > T(0) && phi1 < T(0)); + if (!brackets_zero) + return false; + + const T denom = phi0 - phi1; + const T denom_tol = + std::max(zero_tol, T(64) * std::numeric_limits::epsilon()); + if (std::fabs(denom) <= denom_tol) + return false; + + root_t = std::clamp(phi0 / denom, T(0), T(1)); + return true; +} + +} // anonymous namespace + +// ===================================================================== +// extract_parent_edge_bernstein +// ===================================================================== + +template +void extract_parent_edge_bernstein(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + int parent_local_edge_id, + std::vector& edge_coeffs) +{ + const int p = degree; + edge_coeffs.resize(static_cast(p + 1)); + + auto edge_verts = cell::edges(parent_cell_type); + const int v0 = edge_verts[static_cast(parent_local_edge_id)][0]; + const int v1 = edge_verts[static_cast(parent_local_edge_id)][1]; + + switch (parent_cell_type) + { + case cell::type::interval: + // Trivial: the edge IS the cell. + for (int k = 0; k <= p; ++k) + edge_coeffs[static_cast(k)] = + parent_coeffs[static_cast(k)]; + break; + + case cell::type::triangle: + for (int k = 0; k <= p; ++k) + edge_coeffs[static_cast(k)] = + parent_coeffs[static_cast( + triangle_edge_coeff_index(v0, v1, k, p))]; + break; + + case cell::type::tetrahedron: + for (int k = 0; k <= p; ++k) + edge_coeffs[static_cast(k)] = + parent_coeffs[static_cast( + tetrahedron_edge_coeff_index(v0, v1, k, p))]; + break; + + case cell::type::quadrilateral: + { + // Basix quad vertices: 0=(0,0),1=(1,0),2=(0,1),3=(1,1) + // Basix quad edges: 0={0,1}, 1={0,2}, 2={1,3}, 3={2,3} + // Bernstein tensor-product index: tp_index_2d(ix,iy,p) = iy*(p+1)+ix + for (int k = 0; k <= p; ++k) + { + int ix = 0, iy = 0; + switch (parent_local_edge_id) + { + case 0: ix = k; iy = 0; break; // {0,1}: ix varies, iy=0 + case 1: ix = 0; iy = k; break; // {0,2}: iy varies, ix=0 + case 2: ix = p; iy = k; break; // {1,3}: iy varies, ix=p + case 3: ix = k; iy = p; break; // {2,3}: ix varies, iy=p + } + edge_coeffs[static_cast(k)] = + parent_coeffs[static_cast(tp_index_2d(ix, iy, p))]; + } + break; + } + + case cell::type::hexahedron: + { + // Basix hex vertex numbering: + // 0=(0,0,0), 1=(1,0,0), 2=(0,1,0), 3=(1,1,0), + // 4=(0,0,1), 5=(1,0,1), 6=(0,1,1), 7=(1,1,1) + // For Basix ordering: v = bx + 2*by + 4*bz + // cell_topology.h now returns Basix vertex IDs directly. + for (int k = 0; k <= p; ++k) + { + const int x0 = (v0 & 1) * p, y0 = ((v0 >> 1) & 1) * p, z0 = ((v0 >> 2) & 1) * p; + const int x1 = (v1 & 1) * p, y1 = ((v1 >> 1) & 1) * p, z1 = ((v1 >> 2) & 1) * p; + + const int ix = x0 + (x1 - x0) * k / p; + const int iy = y0 + (y1 - y0) * k / p; + const int iz = z0 + (z1 - z0) * k / p; + + edge_coeffs[static_cast(k)] = + parent_coeffs[static_cast(tp_index_3d(ix, iy, iz, p))]; + } + break; + } + + default: + throw std::runtime_error( + "extract_parent_edge_bernstein: unsupported cell type"); + } +} + +// ===================================================================== +// restrict_edge_bernstein_exact +// ===================================================================== + +template +void restrict_edge_bernstein_exact(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + std::span xi_a, + std::span xi_b, + std::vector& edge_coeffs) +{ + const int p = degree; + const int tdim = cell::get_tdim(parent_cell_type); + if (static_cast(xi_a.size()) != tdim + || static_cast(xi_b.size()) != tdim) + { + throw std::runtime_error( + "restrict_edge_bernstein_exact: endpoint dimension mismatch"); + } + + std::vector power_coeffs(static_cast(p + 1), T(0)); + + if (bernstein::is_simplex(parent_cell_type)) + { + std::vector lambda_a(static_cast(tdim + 1), T(0)); + std::vector lambda_b(static_cast(tdim + 1), T(0)); + + lambda_a[0] = T(1); + lambda_b[0] = T(1); + for (int d = 0; d < tdim; ++d) + { + lambda_a[0] -= xi_a[static_cast(d)]; + lambda_b[0] -= xi_b[static_cast(d)]; + lambda_a[static_cast(d + 1)] = xi_a[static_cast(d)]; + lambda_b[static_cast(d + 1)] = xi_b[static_cast(d)]; + } + + int coeff_index = 0; + if (tdim == 1) + { + for (int i = 0; i <= p; ++i, ++coeff_index) + { + const int alpha[2] = {p - i, i}; + std::vector term = {T(1)}; + for (int c = 0; c < 2; ++c) + { + const std::vector factor = linear_power_poly( + lambda_a[static_cast(c)], + lambda_b[static_cast(c)] - lambda_a[static_cast(c)], + alpha[c]); + term = multiply_poly(term, factor); + } + add_scaled_poly(power_coeffs, std::span(term), + parent_coeffs[static_cast(coeff_index)] + * multinomial(p, alpha, 2)); + } + } + else if (tdim == 2) + { + for (int j = 0; j <= p; ++j) + { + for (int i = 0; i <= p - j; ++i, ++coeff_index) + { + const int alpha[3] = {p - i - j, i, j}; + std::vector term = {T(1)}; + for (int c = 0; c < 3; ++c) + { + const std::vector factor = linear_power_poly( + lambda_a[static_cast(c)], + lambda_b[static_cast(c)] - lambda_a[static_cast(c)], + alpha[c]); + term = multiply_poly(term, factor); + } + add_scaled_poly(power_coeffs, std::span(term), + parent_coeffs[static_cast(coeff_index)] + * multinomial(p, alpha, 3)); + } + } + } + else if (tdim == 3) + { + for (int k = 0; k <= p; ++k) + { + for (int j = 0; j <= p - k; ++j) + { + for (int i = 0; i <= p - k - j; ++i, ++coeff_index) + { + const int alpha[4] = {p - i - j - k, i, j, k}; + std::vector term = {T(1)}; + for (int c = 0; c < 4; ++c) + { + const std::vector factor = linear_power_poly( + lambda_a[static_cast(c)], + lambda_b[static_cast(c)] - lambda_a[static_cast(c)], + alpha[c]); + term = multiply_poly(term, factor); + } + add_scaled_poly(power_coeffs, std::span(term), + parent_coeffs[static_cast(coeff_index)] + * multinomial(p, alpha, 4)); + } + } + } + } + else + { + throw std::runtime_error( + "restrict_edge_bernstein_exact: unsupported simplex tdim"); + } + } + else if (bernstein::is_tensor_product(parent_cell_type)) + { + if (tdim == 2) + { + int coeff_index = 0; + for (int iy = 0; iy <= p; ++iy) + { + for (int ix = 0; ix <= p; ++ix, ++coeff_index) + { + std::vector term = {T(1)}; + const int ids[2] = {ix, iy}; + for (int d = 0; d < 2; ++d) + { + const T a = xi_a[static_cast(d)]; + const T b = xi_b[static_cast(d)] - a; + std::vector dim_poly = linear_power_poly(a, b, ids[d]); + const std::vector one_minus_poly = + linear_power_poly(T(1) - a, -b, p - ids[d]); + dim_poly = multiply_poly(dim_poly, one_minus_poly); + const T scale = binomial(p, ids[d]); + for (T& x : dim_poly) + x *= scale; + term = multiply_poly(term, dim_poly); + } + add_scaled_poly(power_coeffs, std::span(term), + parent_coeffs[static_cast(coeff_index)]); + } + } + } + else if (tdim == 3) + { + int coeff_index = 0; + for (int iz = 0; iz <= p; ++iz) + { + for (int iy = 0; iy <= p; ++iy) + { + for (int ix = 0; ix <= p; ++ix, ++coeff_index) + { + std::vector term = {T(1)}; + const int ids[3] = {ix, iy, iz}; + for (int d = 0; d < 3; ++d) + { + const T a = xi_a[static_cast(d)]; + const T b = xi_b[static_cast(d)] - a; + std::vector dim_poly = linear_power_poly(a, b, ids[d]); + const std::vector one_minus_poly = + linear_power_poly(T(1) - a, -b, p - ids[d]); + dim_poly = multiply_poly(dim_poly, one_minus_poly); + const T scale = binomial(p, ids[d]); + for (T& x : dim_poly) + x *= scale; + term = multiply_poly(term, dim_poly); + } + add_scaled_poly(power_coeffs, std::span(term), + parent_coeffs[static_cast(coeff_index)]); + } + } + } + } + else + { + throw std::runtime_error( + "restrict_edge_bernstein_exact: unsupported tensor-product tdim"); + } + } + else + { + throw std::runtime_error( + "restrict_edge_bernstein_exact: unsupported parent cell type"); + } + + power_to_bernstein_1d(std::span(power_coeffs), edge_coeffs); +} + +// ===================================================================== +// edge_is_on_single_parent_edge +// ===================================================================== + +template +bool edge_is_on_single_parent_edge(const AdaptCell& adapt_cell, + int edge_id, + int& parent_edge_id) +{ + // Get the two vertex indices of this edge. + auto verts = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + assert(verts.size() == 2); + + const int v0 = verts[0]; + const int v1 = verts[1]; + + // Both must be on parent dimension 0 or 1 (vertex or edge). + const int dim0 = adapt_cell.vertex_parent_dim[static_cast(v0)]; + const int dim1 = adapt_cell.vertex_parent_dim[static_cast(v1)]; + + // Collect the set of parent edges each vertex lies on. + // A vertex with parent_dim == 0 (parent vertex) lies on all parent edges + // incident to that vertex. A vertex with parent_dim == 1 lies on exactly + // one parent edge (parent_id). + // For simplicity, and because the initial AdaptCell has only parent-vertex + // provenance, we handle the case where both vertices are parent vertices: + // check if they form a parent edge. + + if (dim0 == 1 && dim1 == 1) + { + // Both on parent edges; must be the same one. + int pe0 = adapt_cell.vertex_parent_id[static_cast(v0)]; + int pe1 = adapt_cell.vertex_parent_id[static_cast(v1)]; + if (pe0 == pe1) + { + parent_edge_id = pe0; + return true; + } + return false; + } + + if (dim0 == 0 && dim1 == 0) + { + // Both are initial parent vertices. In make_adapt_cell the vertices + // are stored in Basix order, so AdaptCell vertex index v == Basix + // local vertex index v. Compare local indices directly against the + // parent edge table (which also uses Basix vertex indices). + auto parent_edges = cell::edges(adapt_cell.parent_cell_type); + for (int e = 0; e < static_cast(parent_edges.size()); ++e) + { + const auto& pe = parent_edges[static_cast(e)]; + if ((pe[0] == v0 && pe[1] == v1) + || (pe[0] == v1 && pe[1] == v0)) + { + parent_edge_id = e; + return true; + } + } + return false; + } + + if ((dim0 == 0 && dim1 == 1) || (dim0 == 1 && dim1 == 0)) + { + // One is a parent vertex, the other is on a parent edge. + // Check that the parent vertex is an endpoint of that parent edge. + int vert_v = (dim0 == 0) ? v0 : v1; + int edge_v = (dim0 == 0) ? v1 : v0; + int gv = adapt_cell.vertex_parent_id[static_cast(vert_v)]; + int pe = adapt_cell.vertex_parent_id[static_cast(edge_v)]; + + auto parent_edges = cell::edges(adapt_cell.parent_cell_type); + const auto& edge_pair = parent_edges[static_cast(pe)]; + if (edge_pair[0] == gv || edge_pair[1] == gv) + { + parent_edge_id = pe; + return true; + } + return false; + } + + // Other combinations (face, interior) → not on a single parent edge. + return false; +} + +// ===================================================================== +// classify_edge_roots +// ===================================================================== + +template +EdgeRootTag classify_edge_roots(std::span edge_coeffs, + T zero_tol, T sign_tol, + int max_depth, + T& green_split_t, + bool& has_green_split_t) +{ + has_green_split_t = false; + green_split_t = T(0); + + if (edge_coeffs.empty()) + return EdgeRootTag::not_classified; + + // 1. All zero? + if (bernstein_all_zero(edge_coeffs, zero_tol)) + return EdgeRootTag::zero; + + // 2. All positive or all negative? + if (bernstein_all_positive(edge_coeffs, sign_tol)) + return EdgeRootTag::no_root; + if (bernstein_all_negative(edge_coeffs, sign_tol)) + return EdgeRootTag::no_root; + + const bool left_zero = std::fabs(edge_coeffs.front()) <= zero_tol; + const bool right_zero = std::fabs(edge_coeffs.back()) <= zero_tol; + + // 3. Find root intervals. + std::vector> intervals; + bool has_zero_segment = false; + find_root_intervals_1d(edge_coeffs, T(0), T(1), + zero_tol, sign_tol, 0, max_depth, + intervals, has_zero_segment); + + if (has_zero_segment) + return EdgeRootTag::zero; + + // --------------------------------------------------------------- + // Check: "one vertex zero AND an interior root" must be multiple_roots. + // + // If one endpoint has phi≈0 (left_zero / right_zero) AND the root + // finder also found a strictly interior interval (t0>0 or t1<1), + // merging them into a single one_root would place the cut at the + // existing vertex (wrong position) and produce degenerate geometry. + // Classify as multiple_roots here, *before* any merge step, so + // green refinement splits the edge between the vertex-zero event + // and the interior sign change. + // --------------------------------------------------------------- + if (left_zero && !intervals.empty()) + { + T ep_zero_end = T(0); // rightmost bound of zero-endpoint intervals + T first_interior = T(1); // leftmost start of strictly interior intervals + bool found_ep = false; + bool found_int = false; + for (const auto& iv : intervals) + { + if (iv.t0 == T(0)) + { + ep_zero_end = std::max(ep_zero_end, iv.t1); + found_ep = true; + } + else // iv.t0 > 0 → strictly interior + { + first_interior = std::min(first_interior, iv.t0); + found_int = true; + } + } + if (found_int) + { + T gap_lo = found_ep ? ep_zero_end : T(0); + green_split_t = (gap_lo + first_interior) * T(0.5); + green_split_t = std::clamp(green_split_t, T(0), T(1)); + has_green_split_t = true; + return EdgeRootTag::multiple_roots; + } + } + if (right_zero && !intervals.empty()) + { + T ep_zero_start = T(1); // leftmost bound of zero-endpoint intervals + T last_interior = T(0); // rightmost end of strictly interior intervals + bool found_ep = false; + bool found_int = false; + for (const auto& iv : intervals) + { + if (iv.t1 == T(1)) + { + ep_zero_start = std::min(ep_zero_start, iv.t0); + found_ep = true; + } + else // iv.t1 < 1 → strictly interior + { + last_interior = std::max(last_interior, iv.t1); + found_int = true; + } + } + if (found_int) + { + T gap_hi = found_ep ? ep_zero_start : T(1); + green_split_t = (last_interior + gap_hi) * T(0.5); + green_split_t = std::clamp(green_split_t, T(0), T(1)); + has_green_split_t = true; + return EdgeRootTag::multiple_roots; + } + } + + // Merge overlapping intervals. + T merge_tol = T(4) * std::numeric_limits::epsilon(); + if (max_depth > 0) + { + T width = T(1); + for (int i = 0; i < max_depth; ++i) + width *= T(0.5); + merge_tol = std::max(merge_tol, T(2) * width); + } + merge_root_intervals(intervals, merge_tol); + + std::vector> clusters = intervals; + + bool left_covered = false; + bool right_covered = false; + for (const auto& interval : clusters) + { + if (interval.t0 <= merge_tol) + left_covered = true; + if (interval.t1 >= T(1) - merge_tol) + right_covered = true; + } + + if (left_zero && !left_covered) + clusters.push_back({T(0), T(0)}); + if (right_zero && !right_covered) + clusters.push_back({T(1), T(1)}); + + if (clusters.empty()) + return EdgeRootTag::no_root; + + std::sort(clusters.begin(), clusters.end(), + [](const EdgeRootInterval& a, const EdgeRootInterval& b) + { return a.t0 < b.t0; }); + merge_root_intervals(clusters, merge_tol); + + if (clusters.size() == 1) + return EdgeRootTag::one_root; + + green_split_t = (clusters[0].t1 + clusters[1].t0) * T(0.5); + green_split_t = std::clamp(green_split_t, T(0), T(1)); + has_green_split_t = true; + return EdgeRootTag::multiple_roots; +} + +// ===================================================================== +// classify_new_edges +// ===================================================================== + +template +void classify_new_edges(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol, + int max_depth) +{ + const int n_edges = adapt_cell.n_entities(1); + + // Ensure tag storage is large enough. + if (adapt_cell.edge_root_tag_num_level_sets <= level_set_id) + adapt_cell.resize_edge_root_tags(level_set_id + 1); + if (adapt_cell.edge_green_split_has_value.size() + < static_cast((level_set_id + 1) * n_edges)) + adapt_cell.resize_green_split_data(level_set_id + 1); + if (adapt_cell.edge_one_root_has_value.size() + < static_cast((level_set_id + 1) * n_edges)) + adapt_cell.resize_one_root_data(level_set_id + 1); + + std::vector edge_coeffs; + + for (int e = 0; e < n_edges; ++e) + { + // Skip already classified edges. + if (adapt_cell.get_edge_root_tag(level_set_id, e) + != EdgeRootTag::not_classified) + continue; + + // Extract edge Bernstein coefficients on the actual adaptive edge segment. + // + // Even if an adaptive edge lies on a parent edge, using the parent-edge + // fast-path can be incorrect for subsegments. Restrict by endpoints so + // the endpoint coefficients and green split parameter use this edge's + // local parameterization. + auto verts = adapt_cell.entity_to_vertex[1][static_cast(e)]; + const int tdim = adapt_cell.tdim; + std::span xi_a( + adapt_cell.vertex_coords.data() + + static_cast(verts[0]) * static_cast(tdim), + static_cast(tdim)); + std::span xi_b( + adapt_cell.vertex_coords.data() + + static_cast(verts[1]) * static_cast(tdim), + static_cast(tdim)); + + restrict_edge_bernstein_exact( + ls_cell.cell_type, ls_cell.bernstein_order, + std::span(ls_cell.bernstein_coeffs), + xi_a, xi_b, edge_coeffs); + + // Classify. + T green_split_t = T(0); + bool has_green = false; + EdgeRootTag tag = classify_edge_roots( + std::span(edge_coeffs), + zero_tol, sign_tol, max_depth, + green_split_t, has_green); + + T linear_root_t = T(0); + const bool has_linear_root = + tag == EdgeRootTag::one_root + && edge_coeffs.size() >= 2 + && linear_one_root_parameter_from_endpoint_values( + edge_coeffs.front(), edge_coeffs.back(), zero_tol, linear_root_t); + if (tag == EdgeRootTag::one_root && !has_linear_root) + tag = EdgeRootTag::no_root; + + adapt_cell.set_edge_root_tag(level_set_id, e, tag); + + const auto idx = static_cast(level_set_id * n_edges + e); + adapt_cell.edge_green_split_param[idx] = green_split_t; + adapt_cell.edge_green_split_has_value[idx] = has_green ? 1 : 0; + if (tag == EdgeRootTag::one_root) + { + adapt_cell.edge_one_root_param[idx] = linear_root_t; + adapt_cell.edge_one_root_vertex_id[idx] = -1; + adapt_cell.edge_one_root_has_value[idx] = 1; + } + else + { + adapt_cell.edge_one_root_param[idx] = T(0); + adapt_cell.edge_one_root_vertex_id[idx] = -1; + adapt_cell.edge_one_root_has_value[idx] = 0; + } + } +} + +// ===================================================================== +// Explicit template instantiations +// ===================================================================== + +template void subdivide_bernstein_1d(std::span, double, + std::vector&, std::vector&); +template void subdivide_bernstein_1d(std::span, float, + std::vector&, std::vector&); + +template bool bernstein_all_positive(std::span, double); +template bool bernstein_all_positive(std::span, float); +template bool bernstein_all_negative(std::span, double); +template bool bernstein_all_negative(std::span, float); +template bool bernstein_all_zero(std::span, double); +template bool bernstein_all_zero(std::span, float); + +template void find_root_intervals_1d(std::span, double, double, + double, double, int, int, + std::vector>&, bool&); +template void find_root_intervals_1d(std::span, float, float, + float, float, int, int, + std::vector>&, bool&); + +template void extract_parent_edge_bernstein(cell::type, int, + std::span, int, + std::vector&); +template void extract_parent_edge_bernstein(cell::type, int, + std::span, int, + std::vector&); + +template void restrict_edge_bernstein_exact(cell::type, int, + std::span, + std::span, + std::span, + std::vector&); +template void restrict_edge_bernstein_exact(cell::type, int, + std::span, + std::span, + std::span, + std::vector&); + +template bool edge_is_on_single_parent_edge(const AdaptCell&, int, int&); +template bool edge_is_on_single_parent_edge(const AdaptCell&, int, int&); + +template EdgeRootTag classify_edge_roots(std::span, double, double, + int, double&, bool&); +template EdgeRootTag classify_edge_roots(std::span, float, float, + int, float&, bool&); + +template void classify_new_edges(AdaptCell&, const LevelSetCell&, + int, double, double, int); +template void classify_new_edges(AdaptCell&, const LevelSetCell&, + int, float, float, int); +template void classify_new_edges(AdaptCell&, const LevelSetCell&, + int, double, double, int); +template void classify_new_edges(AdaptCell&, const LevelSetCell&, + int, float, float, int); + +} // namespace cutcells diff --git a/cpp/src/edge_certification.h b/cpp/src/edge_certification.h new file mode 100644 index 0000000..68a4ed3 --- /dev/null +++ b/cpp/src/edge_certification.h @@ -0,0 +1,197 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +#include "adapt_cell.h" +#include "cell_types.h" +#include "level_set_cell.h" + +namespace cutcells +{ + +// ===================================================================== +// Root-interval helper +// ===================================================================== + +/// A parameter interval [t0, t1] containing a root of a 1D Bernstein polynomial. +template +struct EdgeRootInterval +{ + T t0 = 0; + T t1 = 0; +}; + +// ===================================================================== +// 1D Bernstein subdivision +// ===================================================================== + +/// Exact de Casteljau subdivision of a degree-p 1D Bernstein polynomial at t_split. +/// +/// Given coefficients c_0, ..., c_p, computes left[0..p] and right[0..p] +/// such that: +/// left is the Bernstein representation on [0, t_split] +/// right is the Bernstein representation on [t_split, 1] +/// +/// @param coeffs Bernstein coefficients of degree p (size p+1). +/// @param t_split Parameter in (0,1). +/// @param[out] left Left child coefficients (resized to p+1). +/// @param[out] right Right child coefficients (resized to p+1). +template +void subdivide_bernstein_1d(std::span coeffs, + T t_split, + std::vector& left, + std::vector& right); + +// ===================================================================== +// 1D sign-hull helpers +// ===================================================================== + +/// True if all coefficients are > tol. +template +bool bernstein_all_positive(std::span coeffs, T tol); + +/// True if all coefficients are < -tol. +template +bool bernstein_all_negative(std::span coeffs, T tol); + +/// True if |coeff| <= tol for every coefficient. +template +bool bernstein_all_zero(std::span coeffs, T tol); + +// ===================================================================== +// Recursive root-interval finder +// ===================================================================== + +/// Recursively locate parameter intervals containing roots of a 1D +/// Bernstein polynomial, using convex-hull exclusion and de Casteljau +/// subdivision. +/// +/// This is used only for edge topology classification, not for placing the +/// cut vertex. The committed cut point is placed by linear interpolation of +/// the endpoint level-set values. +/// +/// @param coeffs Bernstein coefficients on the sub-interval [t0, t1]. +/// @param t0, t1 Current parameter bounds (start with 0, 1). +/// @param zero_tol Tolerance for the all-zero test. +/// @param sign_tol Tolerance for the all-positive / all-negative test. +/// @param depth Current recursion depth. +/// @param max_depth Maximum recursion depth. +/// @param[out] intervals Collected root intervals. +/// @param[out] has_zero_segment Set to true if an identically-zero segment is found. +template +void find_root_intervals_1d(std::span coeffs, + T t0, T t1, + T zero_tol, T sign_tol, + int depth, int max_depth, + std::vector>& intervals, + bool& has_zero_segment); + +// ===================================================================== +// Exact edge Bernstein extraction +// ===================================================================== + +/// Extract the Bernstein coefficients of a parent polynomial restricted to +/// a parent edge (local edge id, canonical numbering). +/// +/// For simplex cells this is multi-index slicing. +/// For tensor-product cells this is row/column extraction. +/// +/// @param parent_cell_type Type of the parent cell. +/// @param degree Polynomial degree. +/// @param parent_coeffs Bernstein coefficients on the parent cell. +/// @param parent_local_edge_id Local edge index (cell_topology.h ordering). +/// @param[out] edge_coeffs Bernstein coefficients on the edge (degree+1 entries). +template +void extract_parent_edge_bernstein(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + int parent_local_edge_id, + std::vector& edge_coeffs); + +/// Exact Bernstein restriction to an arbitrary edge [xi_a, xi_b] inside +/// the parent reference cell. +/// +/// Computes the Bernstein coefficients of q(t) = phi((1-t)*xi_a + t*xi_b). +/// +/// @param parent_cell_type Type of the parent cell. +/// @param degree Polynomial degree. +/// @param parent_coeffs Bernstein coefficients on the parent cell. +/// @param xi_a, xi_b Endpoints in parent reference coordinates (size = tdim). +/// @param[out] edge_coeffs Bernstein coefficients on the edge (degree+1 entries). +template +void restrict_edge_bernstein_exact(cell::type parent_cell_type, + int degree, + std::span parent_coeffs, + std::span xi_a, + std::span xi_b, + std::vector& edge_coeffs); + +/// Test whether an adaptive edge lies entirely on a single parent edge. +/// +/// Uses vertex provenance: both endpoints must have parent_dim == 1 (or 0 +/// with the vertex on the same parent edge) and the same parent_edge_id. +/// +/// @param adapt_cell The AdaptCell. +/// @param edge_id Index of the edge in entity_to_vertex[1]. +/// @param[out] parent_edge_id If true, the parent edge id. +/// @return true if the edge lies on a single parent edge. +template +bool edge_is_on_single_parent_edge(const AdaptCell& adapt_cell, + int edge_id, + int& parent_edge_id); + +// ===================================================================== +// Edge classifier +// ===================================================================== + +/// Classify a 1D Bernstein polynomial for root structure. +/// +/// This classification may use Bernstein sign-hull subdivision to detect +/// edge topology, but it does not determine the final cut-vertex location. +/// Cut vertices on one-root edges are placed later by linear interpolation +/// of the two endpoint level-set values. +/// +/// @param edge_coeffs Bernstein coefficients (degree+1 entries). +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +/// @param max_depth Maximum subdivision depth for topology classification. +/// @param[out] green_split_t Parameter between two distinct root intervals +/// (valid only if tag == multiple_roots). +/// @param[out] has_green_split_t True if green_split_t was computed. +/// @return EdgeRootTag. +template +EdgeRootTag classify_edge_roots(std::span edge_coeffs, + T zero_tol, T sign_tol, + int max_depth, + T& green_split_t, + bool& has_green_split_t); + +/// Classify all not-yet-classified edges of an AdaptCell for one level set. +/// +/// For each edge with tag == not_classified: +/// 1. extract exact edge Bernstein from the LevelSetCell +/// 2. classify root topology using Bernstein sign information +/// 3. store tag and optional green-split parameter +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param ls_cell LevelSetCell providing Bernstein coefficients. +/// @param level_set_id Which level set (bit position / name index). +/// @param zero_tol Tolerance for all-zero. +/// @param sign_tol Tolerance for all-positive / all-negative. +/// @param max_depth Maximum subdivision depth for topology classification. +template +void classify_new_edges(AdaptCell& adapt_cell, + const LevelSetCell& ls_cell, + int level_set_id, + T zero_tol, T sign_tol, + int max_depth); + +} // namespace cutcells diff --git a/cpp/src/edge_root.h b/cpp/src/edge_root.h new file mode 100644 index 0000000..5b98baa --- /dev/null +++ b/cpp/src/edge_root.h @@ -0,0 +1,1156 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cutcells::cell::edge_root +{ +template +inline T default_value_tolerance() +{ + return T(64) * std::numeric_limits::epsilon(); +} + +enum class method +{ + linear = 0, + brent = 1, + itp = 2, + newton = 3 +}; + +template +struct RootSolveInfo +{ + T t = T(0.5); + T residual = std::numeric_limits::max(); + int iterations = 0; + int evaluations = 0; + bool converged = false; +}; + +/// Result of a ray root-finding solve: phi(x_s + t*d) = 0. +template +struct RayRootResult +{ + bool valid = false; + bool ambiguous_two_sided = false; + bool used_gradient_dir = false; + bool used_fallback_dir = false; + + T t = T(0); + T phi_at_root = std::numeric_limits::max(); + T dist = std::numeric_limits::max(); + T residual = std::numeric_limits::max(); + int iterations = 0; + int evaluations = 0; + bool converged = false; + bool domain_exit = false; + bool gradient_degenerate = false; + + std::array x_ref = {T(0), T(0), T(0)}; + + bool pos_valid = false; + bool neg_valid = false; + T pos_t = T(0); + T neg_t = T(0); +}; + +template +struct RaySearchOptions +{ + int max_iter = 64; + T xtol = T(1e-12); + T ftol = default_value_tolerance(); + T domain_tol = T(1e-10); + std::array probe_scales = {T(1), T(2), T(4)}; + T max_probe_distance = std::numeric_limits::infinity(); +}; + +template +inline T linear_root_parameter(const T v0, const T v1, const T level = T(0)) +{ + const T denom = v1 - v0; + if (std::abs(denom) <= std::numeric_limits::epsilon()) + return T(0.5); + return (level - v0) / denom; +} + +template +inline void interpolate_point(const std::span p0, const std::span p1, + const T t, const std::span out) +{ + if (p0.size() != p1.size() || p0.size() != out.size()) + throw std::invalid_argument("interpolate_point: inconsistent point dimensions"); + + for (std::size_t i = 0; i < p0.size(); ++i) + out[i] = p0[i] + (p1[i] - p0[i]) * t; +} + +template +inline void linear_intersection_point(const std::span p0, const std::span p1, + const T v0, const T v1, const std::span out, + const T level = T(0)) +{ + const T t = linear_root_parameter(v0, v1, level); + interpolate_point(p0, p1, t, out); +} + +template +inline void linear_intersection_point(const std::span p0, const std::span p1, + const T v0, const T v1, std::vector& out, + const int offset = 0, const T level = T(0)) +{ + if (offset < 0) + throw std::invalid_argument("linear_intersection_point: negative offset"); + if (out.size() < static_cast(offset) + p0.size()) + throw std::invalid_argument("linear_intersection_point: output vector too small"); + + linear_intersection_point(p0, p1, v0, v1, + std::span(out.data() + offset, p0.size()), level); +} + +template +inline T evaluate_edge_function(Phi& phi, const std::span p0, const std::span p1, + const T t) +{ + std::array x = {T(0), T(0), T(0)}; + const std::size_t gdim = p0.size(); + if (gdim > x.size()) + throw std::invalid_argument("evaluate_edge_function: only gdim <= 3 supported"); + + for (std::size_t j = 0; j < gdim; ++j) + x[j] = p0[j] + (p1[j] - p0[j]) * t; + + return static_cast(std::invoke(phi, std::span(x.data(), gdim))); +} + +/// General-purpose Brent solver on an arbitrary real interval [a, b]. +/// +/// Unlike brent_parameter, this does NOT clamp the result to [0, 1]. +/// Use this when the parameter domain extends beyond the unit interval, +/// e.g. for ray-parameter root finding. +/// +/// @param eval callable T(T) — function whose root is sought +/// @param a left bracket +/// @param b right bracket +/// @param fa eval(a) +/// @param fb eval(b) — must satisfy fa * fb <= 0 +/// @param max_iter maximum iterations +/// @param xtol tolerance on bracket width +/// @param ftol tolerance on function value +template +inline T brent_solve(Eval&& eval, T a, T b, T fa, T fb, + int max_iter, T xtol, T ftol, + int* out_iterations = nullptr, + bool* out_converged = nullptr) +{ + if (out_iterations) + *out_iterations = 0; + if (out_converged) + *out_converged = false; + + if (std::abs(fa) <= ftol) + { + if (out_converged) *out_converged = true; + return a; + } + if (std::abs(fb) <= ftol) + { + if (out_converged) *out_converged = true; + return b; + } + if (fa * fb > T(0)) + return T(0.5) * (a + b); + + T c = a, fc = fa; + T d = b - a, e = d; + + for (int iter = 0; iter < max_iter; ++iter) + { + if ((fb > T(0) && fc > T(0)) || (fb < T(0) && fc < T(0))) + { + c = a; fc = fa; d = b - a; e = d; + } + if (std::abs(fc) < std::abs(fb)) + { + a = b; fa = fb; b = c; fb = fc; c = a; fc = fa; + } + + const T eps = std::numeric_limits::epsilon(); + const T tol1 = T(2) * eps * std::abs(b) + T(0.5) * xtol; + const T xm = T(0.5) * (c - b); + + if (std::abs(xm) <= tol1 || std::abs(fb) <= ftol) + { + if (out_iterations) *out_iterations = iter + 1; + if (out_converged) *out_converged = true; + return b; + } + + if (std::abs(e) >= tol1 && std::abs(fa) > std::abs(fb)) + { + T s = fb / fa; + T p, q; + if (a == c) + { + p = T(2) * xm * s; + q = T(1) - s; + } + else + { + T q0 = fa / fc, r = fb / fc; + p = s * (T(2) * xm * q0 * (q0 - r) - (b - a) * (r - T(1))); + q = (q0 - T(1)) * (r - T(1)) * (s - T(1)); + } + if (p > T(0)) q = -q; + p = std::abs(p); + + const T min1 = T(3) * xm * q - std::abs(tol1 * q); + const T min2 = std::abs(e * q); + if (T(2) * p < std::min(min1, min2)) + { e = d; d = p / q; } + else + { d = xm; e = d; } + } + else + { + d = xm; e = d; + } + + a = b; fa = fb; + if (std::abs(d) > tol1) b += d; + else b += (xm > T(0) ? tol1 : -tol1); + fb = eval(b); + } + + if (out_iterations) *out_iterations = max_iter; + return b; +} + +template +inline T brent_parameter(Eval&& eval, T a, T b, T fa, T fb, + const int max_iter, const T xtol, const T ftol, + int* out_iterations = nullptr, + bool* out_converged = nullptr) +{ + if (out_iterations) + *out_iterations = 0; + if (out_converged) + *out_converged = false; + + if (std::abs(fa) <= ftol) + { + if (out_converged) + *out_converged = true; + return a; + } + if (std::abs(fb) <= ftol) + { + if (out_converged) + *out_converged = true; + return b; + } + + if (fa * fb > T(0)) + throw std::domain_error("brent_parameter: no sign change in bracket [a, b]"); + + T c = a; + T fc = fa; + T d = b - a; + T e = d; + + for (int iter = 0; iter < max_iter; ++iter) + { + if ((fb > 0 && fc > 0) || (fb < 0 && fc < 0)) + { + c = a; + fc = fa; + d = b - a; + e = d; + } + + if (std::abs(fc) < std::abs(fb)) + { + // Three-way rotation: new b = c_old (better), new a = b_old, new c = b_old. + // Execute sequentially so that after a = b, 'a' already holds b_old. + a = b; + fa = fb; + b = c; + fb = fc; + c = a; // a is already b_old — maintains bracket [b, c] with f(b)*f(c) ≤ 0 + fc = fa; // fa is already fb_old + } + + const T eps = std::numeric_limits::epsilon(); + const T tol1 = T(2) * eps * std::abs(b) + T(0.5) * xtol; + const T xm = T(0.5) * (c - b); + + if (std::abs(xm) <= tol1 || std::abs(fb) <= ftol) + { + if (out_iterations) + *out_iterations = iter + 1; + if (out_converged) + *out_converged = true; + return b; + } + + if (std::abs(e) >= tol1 && std::abs(fa) > std::abs(fb)) + { + T s = fb / fa; + T p, q; + if (a == c) + { + p = T(2) * xm * s; + q = T(1) - s; + } + else + { + T r = fb / fc; + T q0 = fa / fc; + p = s * (T(2) * xm * q0 * (q0 - r) - (b - a) * (r - T(1))); + q = (q0 - T(1)) * (r - T(1)) * (s - T(1)); + } + + if (p > T(0)) + q = -q; + p = std::abs(p); + T min1 = T(3) * xm * q - std::abs(tol1 * q); + T min2 = std::abs(e * q); + if (T(2) * p < std::min(min1, min2)) + { + e = d; + d = p / q; + } + else + { + d = xm; + e = d; + } + } + else + { + d = xm; + e = d; + } + + a = b; + fa = fb; + if (std::abs(d) > tol1) + b += d; + else + b += (xm > 0 ? tol1 : -tol1); + fb = eval(b); + } + + if (out_iterations) + *out_iterations = max_iter; + return b; +} + +template +inline T itp_parameter(Eval&& eval, T a, T b, T fa, T fb, const T initial_guess, + const int max_iter, const T xtol, const T ftol, + int* out_iterations = nullptr, + bool* out_converged = nullptr) +{ + if (out_iterations) + *out_iterations = 0; + if (out_converged) + *out_converged = false; + + if (std::abs(fa) <= ftol) + { + if (out_converged) + *out_converged = true; + return a; + } + if (std::abs(fb) <= ftol) + { + if (out_converged) + *out_converged = true; + return b; + } + + if (fa * fb > T(0)) + throw std::domain_error("itp_parameter: no sign change in bracket [a, b]"); + + // Use the linear guess first to contract the bracket if possible. + const T t0 = std::clamp(initial_guess, a, b); + { + const T f0 = eval(t0); + if (std::abs(f0) <= ftol) + { + if (out_converged) + *out_converged = true; + return t0; + } + if (fa * f0 <= T(0)) + { + b = t0; + fb = f0; + } + else if (f0 * fb <= T(0)) + { + a = t0; + fa = f0; + } + } + + const T width0 = b - a; + const int n_max = std::max(0, static_cast(std::ceil(std::log2(width0 / xtol)))); + const T k1 = T(0.2) / std::max(width0, std::numeric_limits::epsilon()); + const T k2 = T(2); + const int iter_max = std::min(max_iter, n_max + 8); + + for (int iter = 0; iter < iter_max; ++iter) + { + const T width = b - a; + if (width <= xtol) + { + if (out_iterations) + *out_iterations = iter + 1; + if (out_converged) + *out_converged = true; + return T(0.5) * (a + b); + } + + const T m = T(0.5) * (a + b); + const T xf = (a * fb - b * fa) / (fb - fa); + const T sigma = (m >= xf) ? T(1) : T(-1); + + // Truncation: delta = min(k1 * width^k2, |m - xf|) + // Then xt = xf + sigma * delta (clamps between xf and m). + const T delta = std::min(k1 * std::pow(width, k2), std::abs(m - xf)); + const T xt = xf + sigma * delta; + + T r = xtol * std::ldexp(T(1), std::max(0, n_max - iter)) - T(0.5) * width; + r = std::max(T(0), r); + + T x = (std::abs(xt - m) <= r) ? xt : (m - sigma * r); + x = std::clamp(x, a, b); + const T fx = eval(x); + + if (std::abs(fx) <= ftol) + { + if (out_iterations) + *out_iterations = iter + 1; + if (out_converged) + *out_converged = true; + return x; + } + + if (fa * fx <= T(0)) + { + b = x; + fb = fx; + } + else + { + a = x; + fa = fx; + } + } + + if (out_iterations) + *out_iterations = iter_max; + return T(0.5) * (a + b); +} + +template +inline T newton_parameter(Eval&& eval, T a, T b, T fa, T fb, const T initial_guess, + const int max_iter, const T xtol, const T ftol, + int* out_iterations = nullptr, + bool* out_converged = nullptr) +{ + if (out_iterations) + *out_iterations = 0; + if (out_converged) + *out_converged = false; + + if (std::abs(fa) <= ftol) + { + if (out_converged) + *out_converged = true; + return a; + } + if (std::abs(fb) <= ftol) + { + if (out_converged) + *out_converged = true; + return b; + } + if (fa * fb > T(0)) + throw std::domain_error("newton_parameter: no sign change in bracket [a, b]"); + + T l = a; + T r = b; + T fl = fa; + T fr = fb; + T t = std::clamp(initial_guess, l, r); + const T eps = std::numeric_limits::epsilon(); + + for (int iter = 0; iter < max_iter; ++iter) + { + if ((r - l) <= xtol) + { + if (out_iterations) + *out_iterations = iter + 1; + if (out_converged) + *out_converged = true; + return T(0.5) * (l + r); + } + + const T f = eval(t); + if (std::abs(f) <= ftol) + { + if (out_iterations) + *out_iterations = iter + 1; + if (out_converged) + *out_converged = true; + return t; + } + + if (fl * f <= T(0)) + { + r = t; + fr = f; + } + else if (f * fr <= T(0)) + { + l = t; + fl = f; + } + + const T width = r - l; + T h = std::sqrt(eps) * std::max(T(1), std::abs(t)); + h = std::max(h, T(0.01) * width); + h = std::min(h, T(0.25) * width); + h = std::max(h, xtol); + + const T t_minus = std::max(l, t - h); + const T t_plus = std::min(r, t + h); + T t_candidate = T(0.5) * (l + r); + + if (t_plus > t_minus + xtol) + { + const T f_minus = eval(t_minus); + const T f_plus = eval(t_plus); + const T df = (f_plus - f_minus) / (t_plus - t_minus); + + if (std::isfinite(df) && std::abs(df) > T(10) * eps) + { + const T t_newton = t - f / df; + if (std::isfinite(t_newton) && t_newton > l && t_newton < r) + t_candidate = t_newton; + } + } + + if (!(t_candidate > l && t_candidate < r)) + t_candidate = T(0.5) * (l + r); + t = t_candidate; + } + + if (out_iterations) + *out_iterations = max_iter; + return t; +} + +template +inline RootSolveInfo find_root_parameter_info(const std::span p0, + const std::span p1, + Phi&& level_set, + const method root_method = method::linear, + const T level = T(0), + const int max_iter = 64, + const T xtol = T(1e-12), + const T ftol = default_value_tolerance()) +{ + if (p0.size() != p1.size()) + throw std::invalid_argument("find_root_parameter: inconsistent point dimensions"); + + RootSolveInfo info; + int eval_count = 0; + auto eval = [&](const T t) -> T + { + ++eval_count; + return evaluate_edge_function(level_set, p0, p1, t) - level; + }; + + T a = T(0); + T b = T(1); + T fa = eval(a); + T fb = eval(b); + const T a0 = a; + const T b0 = b; + const T fa0 = fa; + const T fb0 = fb; + + if (std::abs(fa) <= ftol) + { + info.t = T(0); + info.residual = std::abs(fa); + info.evaluations = eval_count; + info.converged = true; + return info; + } + if (std::abs(fb) <= ftol) + { + info.t = T(1); + info.residual = std::abs(fb); + info.evaluations = eval_count; + info.converged = true; + return info; + } + + const T t_linear = linear_root_parameter(fa, fb, T(0)); + if (root_method == method::linear) + { + const T f_linear = eval(t_linear); + info.t = t_linear; + info.residual = std::abs(f_linear); + info.evaluations = eval_count; + info.converged = (std::abs(f_linear) <= ftol); + return info; + } + + // Contract the bracket with the linear guess when possible. + bool have_f_linear = false; + T f_linear = T(0); + if (t_linear > T(0) && t_linear < T(1)) + { + f_linear = eval(t_linear); + have_f_linear = true; + if (std::abs(f_linear) <= ftol) + { + info.t = t_linear; + info.residual = std::abs(f_linear); + info.evaluations = eval_count; + info.converged = true; + return info; + } + if (fa * f_linear <= T(0)) + { + b = t_linear; + fb = f_linear; + } + else if (f_linear * fb <= T(0)) + { + a = t_linear; + fa = f_linear; + } + } + + if (fa * fb > T(0)) + { + if (!have_f_linear) + f_linear = eval(t_linear); + info.t = t_linear; + info.residual = std::abs(f_linear); + info.evaluations = eval_count; + return info; + } + + int iterations = 0; + bool converged = false; + T t = t_linear; + + if (root_method == method::brent) + { + t = brent_parameter(eval, a, b, fa, fb, max_iter, xtol, ftol, &iterations, &converged); + } + else if (root_method == method::itp) + { + t = itp_parameter(eval, a, b, fa, fb, t_linear, max_iter, xtol, ftol, &iterations, &converged); + } + else + { + t = newton_parameter(eval, a, b, fa, fb, t_linear, max_iter, xtol, ftol, &iterations, &converged); + } + + const T f = eval(t); + T f_abs = std::abs(f); + + // Safety fallback: if nonlinear solver returned a poor residual while we + // have a valid sign-changing bracket, finish with robust bisection. + auto bisect_on_bracket = [&](T left, T right, T f_left, T f_right) -> T + { + if (f_left * f_right > T(0)) + return t; + + T l = left; + T r = right; + T fl = f_left; + T fr = f_right; + T mid = T(0.5) * (l + r); + for (int k = 0; k < max_iter; ++k) + { + mid = T(0.5) * (l + r); + const T fm = eval(mid); + ++iterations; + if (std::abs(fm) <= ftol || std::abs(r - l) <= xtol) + return mid; + + if (fl * fm <= T(0)) + { + r = mid; + fr = fm; + } + else + { + l = mid; + fl = fm; + } + } + (void)fr; + return mid; + }; + + if (f_abs > ftol) + { + if (fa * fb <= T(0)) + { + t = bisect_on_bracket(a, b, fa, fb); + } + else if (fa0 * fb0 <= T(0)) + { + t = bisect_on_bracket(a0, b0, fa0, fb0); + } + f_abs = std::abs(eval(t)); + } + + info.t = t; + info.residual = f_abs; + info.iterations = iterations; + info.evaluations = eval_count; + info.converged = (f_abs <= ftol) || (converged && f_abs <= T(100) * ftol); + return info; +} + +template +inline T find_root_parameter(const std::span p0, const std::span p1, Phi&& level_set, + const method root_method = method::linear, + const T level = T(0), const int max_iter = 64, + const T xtol = T(1e-12), + const T ftol = default_value_tolerance()) +{ + return find_root_parameter_info(p0, p1, std::forward(level_set), + root_method, level, max_iter, xtol, ftol).t; +} + +template +inline void find_root_point(const std::span p0, const std::span p1, + const std::span root_point, Phi&& level_set, + const method root_method = method::linear, + const T level = T(0), const int max_iter = 64, + const T xtol = T(1e-12), + const T ftol = default_value_tolerance()) +{ + const T t = find_root_parameter(p0, p1, std::forward(level_set), + root_method, level, max_iter, xtol, ftol); + interpolate_point(p0, p1, t, root_point); +} + +template +inline void compute_case_intersections_from_edge_ids( + const std::span vertex_coordinates, const int gdim, + const std::span ls_values, + const int (*edges)[2], const int num_edges, + const std::span intersected_edge_ids, + std::vector& intersection_points, + EdgeMap& edge_ip_map) +{ + if (gdim <= 0 || gdim > 3) + throw std::invalid_argument("compute_case_intersections_from_edge_ids: gdim must be 1..3"); + + intersection_points.resize(intersected_edge_ids.size() * static_cast(gdim)); + + std::array p0 = {}; + std::array p1 = {}; + + for (std::size_t ip = 0; ip < intersected_edge_ids.size(); ++ip) + { + const int edge_id = intersected_edge_ids[ip]; + if (edge_id < 0 || edge_id >= num_edges) + throw std::invalid_argument("compute_case_intersections_from_edge_ids: invalid edge id"); + + const int v0 = edges[edge_id][0]; + const int v1 = edges[edge_id][1]; + + for (int j = 0; j < gdim; ++j) + { + p0[static_cast(j)] = vertex_coordinates[v0 * gdim + j]; + p1[static_cast(j)] = vertex_coordinates[v1 * gdim + j]; + } + + linear_intersection_point(std::span(p0.data(), static_cast(gdim)), + std::span(p1.data(), static_cast(gdim)), + ls_values[v0], ls_values[v1], + intersection_points, static_cast(ip * gdim), T(0)); + edge_ip_map[edge_id] = static_cast(ip); + } +} + +template +inline void compute_case_intersections_from_edge_mask( + const std::span vertex_coordinates, const int gdim, + const std::span ls_values, + const int (*edges)[2], const int num_edges, + const std::span intersected_edge_mask, + std::vector& intersection_points, + EdgeMap& edge_ip_map) +{ + if (gdim <= 0 || gdim > 3) + throw std::invalid_argument("compute_case_intersections_from_edge_mask: gdim must be 1..3"); + if (intersected_edge_mask.size() != static_cast(num_edges)) + throw std::invalid_argument("compute_case_intersections_from_edge_mask: invalid mask size"); + + intersection_points.clear(); + intersection_points.reserve(static_cast(num_edges) * static_cast(gdim)); + + std::array p0 = {}; + std::array p1 = {}; + + int ip = 0; + for (int edge_id = 0; edge_id < num_edges; ++edge_id) + { + if (intersected_edge_mask[static_cast(edge_id)] == 0) + continue; + + const int v0 = edges[edge_id][0]; + const int v1 = edges[edge_id][1]; + + for (int j = 0; j < gdim; ++j) + { + p0[static_cast(j)] = vertex_coordinates[v0 * gdim + j]; + p1[static_cast(j)] = vertex_coordinates[v1 * gdim + j]; + } + + const int offset = static_cast(intersection_points.size()); + intersection_points.resize(intersection_points.size() + static_cast(gdim)); + linear_intersection_point(std::span(p0.data(), static_cast(gdim)), + std::span(p1.data(), static_cast(gdim)), + ls_values[v0], ls_values[v1], intersection_points, offset, T(0)); + edge_ip_map[edge_id] = ip++; + } +} + +/// Check whether a reference point is inside the parent cell reference domain. +/// +/// @param x_ref reference coordinates, size = tdim +/// @param cell_type cell type defining the reference domain +/// @param tol tolerance for boundary proximity +template +inline bool is_inside_reference_domain( + std::span x_ref, + cutcells::cell::type cell_type, + T tol = T(1e-10)) +{ + using cutcells::cell::type; + switch (cell_type) + { + case type::interval: + { + return x_ref[0] >= -tol && x_ref[0] <= T(1) + tol; + } + case type::triangle: + { + const T x = x_ref[0]; + const T y = x_ref[1]; + return x >= -tol && y >= -tol && (x + y) <= T(1) + tol; + } + case type::tetrahedron: + { + const T x = x_ref[0]; + const T y = x_ref[1]; + const T z = x_ref[2]; + return x >= -tol && y >= -tol && z >= -tol && (x + y + z) <= T(1) + tol; + } + case type::quadrilateral: + { + return x_ref[0] >= -tol && x_ref[0] <= T(1) + tol + && x_ref[1] >= -tol && x_ref[1] <= T(1) + tol; + } + case type::hexahedron: + { + return x_ref[0] >= -tol && x_ref[0] <= T(1) + tol + && x_ref[1] >= -tol && x_ref[1] <= T(1) + tol + && x_ref[2] >= -tol && x_ref[2] <= T(1) + tol; + } + case type::prism: + { + // Prism: triangle in (x,y) x interval in z + const T x = x_ref[0]; + const T y = x_ref[1]; + const T z = x_ref[2]; + return x >= -tol && y >= -tol && (x + y) <= T(1) + tol + && z >= -tol && z <= T(1) + tol; + } + case type::pyramid: + { + // Pyramid: x,y in [0, 1-z], z in [0, 1] + const T x = x_ref[0]; + const T y = x_ref[1]; + const T z = x_ref[2]; + const T limit = T(1) - z; + return z >= -tol && z <= T(1) + tol + && x >= -tol && x <= limit + tol + && y >= -tol && y <= limit + tol; + } + default: + return false; + } +} + +/// Solve phi(x_s + t*d) = 0 for t using Newton → Brent → bisection fallback chain. +/// +/// @param eval_phi callable (const T* x_ref, int tdim) -> T +/// @param eval_grad callable (const T* x_ref, int tdim, T* grad_out) -> void (reference-space gradient) +/// @param x_s starting point in reference coordinates, size = tdim +/// @param d ray direction in reference coordinates, size = tdim +/// @param cell_type reference domain for the domain-exit check +/// @param tdim topological dimension +/// @param h_local local entity size for scale-aware bracket probing (segment length or face diameter) +/// @param tol convergence tolerance +template +inline RayRootResult find_ray_root_one_direction( + EvalPhi&& eval_phi, + EvalGrad&& eval_grad, + std::span x_s, + std::span d, + cutcells::cell::type cell_type, + int tdim, + T h_local, + int sign, + const RaySearchOptions& opts) +{ + RayRootResult result; + if (sign != 1 && sign != -1) + throw std::invalid_argument("find_ray_root_one_direction: sign must be +1 or -1"); + if (tdim <= 0 || tdim > 3) + throw std::invalid_argument("find_ray_root_one_direction: tdim must be in [1, 3]"); + + // Compute norm of direction + T d_norm = T(0); + for (int k = 0; k < tdim; ++k) + d_norm += d[k] * d[k]; + d_norm = std::sqrt(d_norm); + + if (d_norm < opts.ftol) + { + result.gradient_degenerate = true; + return result; + } + + // Normalized direction + std::vector d_hat(static_cast(tdim)); + for (int k = 0; k < tdim; ++k) + d_hat[static_cast(k)] = d[k] / d_norm; + + // Helper: evaluate phi at x_s + t * d_hat + std::vector x_tmp(static_cast(tdim)); + const auto phi_at_t = [&](T t_val) -> T + { + for (int k = 0; k < tdim; ++k) + x_tmp[static_cast(k)] = x_s[k] + t_val * d_hat[static_cast(k)]; + ++result.evaluations; + return eval_phi(x_tmp.data(), tdim); + }; + + T t_a = T(0); + T f_a = phi_at_t(T(0)); + + // Check if already at root + if (std::abs(f_a) <= opts.ftol) + { + result.t = T(0); + result.phi_at_root = f_a; + result.dist = T(0); + result.residual = std::abs(f_a); + result.converged = true; + result.valid = true; + for (int k = 0; k < tdim; ++k) + result.x_ref[static_cast(k)] = x_s[static_cast(k)]; + return result; + } + + // Find a bracket + T t_b = T(0); + T f_b = T(0); + bool have_bracket = false; + + for (const T scale : opts.probe_scales) + { + const T unclamped = scale * h_local; + const T max_probe = std::isfinite(opts.max_probe_distance) + ? std::min(unclamped, opts.max_probe_distance) + : unclamped; + const T t_probe = static_cast(sign) * max_probe; + // Check that probe is inside reference domain + for (int k = 0; k < tdim; ++k) + x_tmp[static_cast(k)] = x_s[k] + t_probe * d_hat[static_cast(k)]; + if (!is_inside_reference_domain( + std::span(x_tmp.data(), static_cast(tdim)), + cell_type, opts.domain_tol)) + continue; + + const T f_probe = phi_at_t(t_probe); + if (std::abs(f_probe) <= opts.ftol) + { + result.t = t_probe; + result.phi_at_root = f_probe; + result.dist = std::abs(t_probe); + result.residual = std::abs(f_probe); + result.converged = true; + result.valid = true; + for (int k = 0; k < tdim; ++k) + result.x_ref[static_cast(k)] + = x_s[static_cast(k)] + t_probe * d_hat[static_cast(k)]; + return result; + } + + if (f_a * f_probe <= T(0)) + { + t_b = t_probe; + f_b = f_probe; + have_bracket = true; + break; + } + + // Keep the smaller-magnitude endpoint as t_a + if (std::abs(f_probe) < std::abs(f_a)) + { + t_a = t_probe; + f_a = f_probe; + } + } + + if (!have_bracket) + { + // No sign change found; return best estimate + result.t = t_a; + result.phi_at_root = f_a; + result.dist = std::abs(t_a); + result.residual = std::abs(f_a); + result.converged = false; + return result; + } + + // Ensure t_a < t_b + if (t_a > t_b) + { + std::swap(t_a, t_b); + std::swap(f_a, f_b); + } + + // Newton iterations with bracket safety + const int max_iter = opts.max_iter; + T t = T(0.5) * (t_a + t_b); + // Try initial Newton step from t_a + { + std::vector grad(static_cast(tdim)); + for (int k = 0; k < tdim; ++k) + x_tmp[static_cast(k)] = x_s[k] + t_a * d_hat[static_cast(k)]; + eval_grad(x_tmp.data(), tdim, grad.data()); + T dphidt = T(0); + for (int k = 0; k < tdim; ++k) + dphidt += grad[static_cast(k)] * d_hat[static_cast(k)]; + if (std::abs(dphidt) > T(1e-14)) + { + const T t_newton = t_a - f_a / dphidt; + if (t_newton > t_a && t_newton < t_b) + t = t_newton; + } + } + + // Brent fallback (unclamped — ray parameter can be negative) + int brent_iters = 0; + bool brent_converged = false; + const T t_brent = brent_solve( + phi_at_t, t_a, t_b, f_a, f_b, + max_iter, opts.xtol, opts.ftol, &brent_iters, &brent_converged); + result.iterations += brent_iters; + t = t_brent; + + const T f_final = phi_at_t(t); + result.t = t; + result.phi_at_root = f_final; + result.dist = std::abs(t); + result.residual = std::abs(f_final); + result.converged = (result.residual <= opts.ftol) || brent_converged; + + // Check domain exit + for (int k = 0; k < tdim; ++k) + x_tmp[static_cast(k)] = x_s[k] + t * d_hat[static_cast(k)]; + result.domain_exit = !is_inside_reference_domain( + std::span(x_tmp.data(), static_cast(tdim)), + cell_type, opts.domain_tol); + result.valid = result.converged && !result.domain_exit; + for (int k = 0; k < tdim; ++k) + result.x_ref[static_cast(k)] = x_tmp[static_cast(k)]; + + return result; +} + +template +inline RayRootResult find_ray_root( + EvalPhi&& eval_phi, + EvalGrad&& eval_grad, + std::span x_s, + std::span d, + cutcells::cell::type cell_type, + int tdim, + T h_local, + const RaySearchOptions& opts) +{ + auto pos = find_ray_root_one_direction( + eval_phi, eval_grad, x_s, d, cell_type, tdim, h_local, +1, opts); + auto neg = find_ray_root_one_direction( + eval_phi, eval_grad, x_s, d, cell_type, tdim, h_local, -1, opts); + + RayRootResult out; + out.evaluations = pos.evaluations + neg.evaluations; + out.iterations = pos.iterations + neg.iterations; + out.gradient_degenerate = pos.gradient_degenerate && neg.gradient_degenerate; + + out.pos_valid = pos.valid; + out.neg_valid = neg.valid; + out.pos_t = pos.t; + out.neg_t = neg.t; + out.ambiguous_two_sided = pos.valid && neg.valid; + + if (pos.valid && neg.valid) + out = (std::abs(pos.t) <= std::abs(neg.t)) ? pos : neg; + else if (pos.valid) + out = pos; + else if (neg.valid) + out = neg; + else + out = (pos.residual <= neg.residual) ? pos : neg; + + out.pos_valid = pos.valid; + out.neg_valid = neg.valid; + out.pos_t = pos.t; + out.neg_t = neg.t; + out.ambiguous_two_sided = pos.valid && neg.valid; + out.evaluations = pos.evaluations + neg.evaluations; + out.iterations = pos.iterations + neg.iterations; + out.gradient_degenerate = pos.gradient_degenerate && neg.gradient_degenerate; + return out; +} + +} // namespace cutcells::cell::edge_root diff --git a/cpp/src/generated/quadrature_tables_hexahedron.h b/cpp/src/generated/quadrature_tables_hexahedron.h new file mode 100644 index 0000000..f2f711b --- /dev/null +++ b/cpp/src/generated/quadrature_tables_hexahedron.h @@ -0,0 +1,762 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'hexahedron', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 1 point(s), tdim=3 ---- +inline constexpr int hexahedron_o1_npts = 1; +inline constexpr int hexahedron_o1_tdim = 3; +inline constexpr double hexahedron_o1_points[3] = { + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01 +}; +inline constexpr double hexahedron_o1_weights[1] = { + 1.000000000000000000e+00 +}; + +// ---- order 2: 8 point(s), tdim=3 ---- +inline constexpr int hexahedron_o2_npts = 8; +inline constexpr int hexahedron_o2_tdim = 3; +inline constexpr double hexahedron_o2_points[24] = { + 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, + 7.886751345948128655e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, 7.886751345948128655e-01 +}; +inline constexpr double hexahedron_o2_weights[8] = { + 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, + 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01 +}; + +// ---- order 3: 8 point(s), tdim=3 ---- +inline constexpr int hexahedron_o3_npts = 8; +inline constexpr int hexahedron_o3_tdim = 3; +inline constexpr double hexahedron_o3_points[24] = { + 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, + 7.886751345948128655e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, + 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01, 7.886751345948128655e-01 +}; +inline constexpr double hexahedron_o3_weights[8] = { + 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, + 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01, 1.250000000000000000e-01 +}; + +// ---- order 4: 27 point(s), tdim=3 ---- +inline constexpr int hexahedron_o4_npts = 27; +inline constexpr int hexahedron_o4_tdim = 3; +inline constexpr double hexahedron_o4_points[81] = { + 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 1.127016653792582979e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, + 5.000000000000000000e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01 +}; +inline constexpr double hexahedron_o4_weights[27] = { + 2.143347050754455807e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 8.779149519890259989e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 2.143347050754455807e-02 +}; + +// ---- order 5: 27 point(s), tdim=3 ---- +inline constexpr int hexahedron_o5_npts = 27; +inline constexpr int hexahedron_o5_tdim = 3; +inline constexpr double hexahedron_o5_points[81] = { + 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 1.127016653792582979e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, + 5.000000000000000000e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01 +}; +inline constexpr double hexahedron_o5_weights[27] = { + 2.143347050754455807e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 8.779149519890259989e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 5.486968449931410585e-02, 3.429355281207130401e-02, 2.143347050754455807e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 5.486968449931410585e-02, 3.429355281207130401e-02, + 2.143347050754455807e-02, 3.429355281207130401e-02, 2.143347050754455807e-02 +}; + +// ---- order 6: 64 point(s), tdim=3 ---- +inline constexpr int hexahedron_o6_npts = 64; +inline constexpr int hexahedron_o6_tdim = 3; +inline constexpr double hexahedron_o6_points[192] = { + 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.943184420297371373e-02, 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.943184420297371373e-02, 9.305681557970262308e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, 9.305681557970262308e-01 +}; +inline constexpr double hexahedron_o6_weights[64] = { + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 1.849254200709765650e-02, 3.466912086923912978e-02, 3.466912086923912284e-02, 1.849254200709765650e-02, + 1.849254200709764956e-02, 3.466912086923912284e-02, 3.466912086923910896e-02, 1.849254200709764956e-02, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 1.849254200709764956e-02, 3.466912086923912284e-02, 3.466912086923910896e-02, 1.849254200709764956e-02, + 1.849254200709764262e-02, 3.466912086923910896e-02, 3.466912086923909508e-02, 1.849254200709764262e-02, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03 +}; + +// ---- order 7: 64 point(s), tdim=3 ---- +inline constexpr int hexahedron_o7_npts = 64; +inline constexpr int hexahedron_o7_tdim = 3; +inline constexpr double hexahedron_o7_points[192] = { + 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.943184420297371373e-02, 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.943184420297371373e-02, 9.305681557970262308e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 3.300094782075718713e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.943184420297371373e-02, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.943184420297371373e-02, 3.300094782075718713e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 6.699905217924281287e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 9.305681557970262308e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01, 9.305681557970262308e-01 +}; +inline constexpr double hexahedron_o7_weights[64] = { + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 1.849254200709765650e-02, 3.466912086923912978e-02, 3.466912086923912284e-02, 1.849254200709765650e-02, + 1.849254200709764956e-02, 3.466912086923912284e-02, 3.466912086923910896e-02, 1.849254200709764956e-02, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 1.849254200709764956e-02, 3.466912086923912284e-02, 3.466912086923910896e-02, 1.849254200709764956e-02, + 1.849254200709764262e-02, 3.466912086923910896e-02, 3.466912086923909508e-02, 1.849254200709764262e-02, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03, + 9.863939474383808664e-03, 1.849254200709765650e-02, 1.849254200709764956e-02, 9.863939474383808664e-03, + 9.863939474383806930e-03, 1.849254200709765303e-02, 1.849254200709764609e-02, 9.863939474383806930e-03, + 5.261434686316423560e-03, 9.863939474383808664e-03, 9.863939474383806930e-03, 5.261434686316423560e-03 +}; + +// ---- order 8: 125 point(s), tdim=3 ---- +inline constexpr int hexahedron_o8_npts = 125; +inline constexpr int hexahedron_o8_tdim = 3; +inline constexpr double hexahedron_o8_points[375] = { + 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, + 4.691007703066801815e-02, 2.307653449471584461e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 5.000000000000000000e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 7.692346550528414983e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 9.530899229693319263e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 5.000000000000000000e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 4.691007703066801815e-02, + 4.691007703066801815e-02, 7.692346550528414983e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 7.692346550528414983e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 9.530899229693319263e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 9.530899229693319263e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 4.691007703066801815e-02, 5.000000000000000000e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 7.692346550528414983e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 9.530899229693319263e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 5.000000000000000000e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 7.692346550528414983e-01, + 2.307653449471584461e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 7.692346550528414983e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 7.692346550528414983e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 9.530899229693319263e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 9.530899229693319263e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 9.530899229693319263e-01 +}; +inline constexpr double hexahedron_o8_weights[125] = { + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 1.370585530681736781e-02, + 1.629051763370604339e-02, 1.370585530681736781e-02, 6.784561404328525239e-03, 8.064000000000001722e-03, + 1.629051763370604339e-02, 1.936259787027560381e-02, 1.629051763370604339e-02, 8.064000000000001722e-03, + 6.784561404328525239e-03, 1.370585530681736781e-02, 1.629051763370604339e-02, 1.370585530681736781e-02, + 6.784561404328525239e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 1.629051763370604339e-02, 1.936259787027560381e-02, 1.629051763370604339e-02, 8.064000000000001722e-03, + 9.584716258668151204e-03, 1.936259787027560728e-02, 2.301401371742111193e-02, 1.936259787027560728e-02, + 9.584716258668151204e-03, 8.064000000000001722e-03, 1.629051763370604339e-02, 1.936259787027560381e-02, + 1.629051763370604339e-02, 8.064000000000001722e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 1.370585530681736781e-02, 1.629051763370604339e-02, 1.370585530681736781e-02, + 6.784561404328525239e-03, 8.064000000000001722e-03, 1.629051763370604339e-02, 1.936259787027560381e-02, + 1.629051763370604339e-02, 8.064000000000001722e-03, 6.784561404328525239e-03, 1.370585530681736781e-02, + 1.629051763370604339e-02, 1.370585530681736781e-02, 6.784561404328525239e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03 +}; + +// ---- order 9: 125 point(s), tdim=3 ---- +inline constexpr int hexahedron_o9_npts = 125; +inline constexpr int hexahedron_o9_tdim = 3; +inline constexpr double hexahedron_o9_points[375] = { + 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, + 4.691007703066801815e-02, 2.307653449471584461e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 5.000000000000000000e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 7.692346550528414983e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 9.530899229693319263e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 5.000000000000000000e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 4.691007703066801815e-02, + 4.691007703066801815e-02, 7.692346550528414983e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 7.692346550528414983e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 9.530899229693319263e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 9.530899229693319263e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 4.691007703066801815e-02, 5.000000000000000000e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 7.692346550528414983e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, + 9.530899229693319263e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, + 5.000000000000000000e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 7.692346550528414983e-01, + 2.307653449471584461e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, + 7.692346550528414983e-01, 4.691007703066801815e-02, 2.307653449471584461e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 2.307653449471584461e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 4.691007703066801815e-02, 7.692346550528414983e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 4.691007703066801815e-02, 9.530899229693319263e-01, + 4.691007703066801815e-02, 2.307653449471584461e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, + 5.000000000000000000e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 2.307653449471584461e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 5.000000000000000000e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, 5.000000000000000000e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 9.530899229693319263e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 7.692346550528414983e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 7.692346550528414983e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, + 9.530899229693319263e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01, 9.530899229693319263e-01 +}; +inline constexpr double hexahedron_o9_weights[125] = { + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 1.370585530681736781e-02, + 1.629051763370604339e-02, 1.370585530681736781e-02, 6.784561404328525239e-03, 8.064000000000001722e-03, + 1.629051763370604339e-02, 1.936259787027560381e-02, 1.629051763370604339e-02, 8.064000000000001722e-03, + 6.784561404328525239e-03, 1.370585530681736781e-02, 1.629051763370604339e-02, 1.370585530681736781e-02, + 6.784561404328525239e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 1.629051763370604339e-02, 1.936259787027560381e-02, 1.629051763370604339e-02, 8.064000000000001722e-03, + 9.584716258668151204e-03, 1.936259787027560728e-02, 2.301401371742111193e-02, 1.936259787027560728e-02, + 9.584716258668151204e-03, 8.064000000000001722e-03, 1.629051763370604339e-02, 1.936259787027560381e-02, + 1.629051763370604339e-02, 8.064000000000001722e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 1.370585530681736781e-02, 1.629051763370604339e-02, 1.370585530681736781e-02, + 6.784561404328525239e-03, 8.064000000000001722e-03, 1.629051763370604339e-02, 1.936259787027560381e-02, + 1.629051763370604339e-02, 8.064000000000001722e-03, 6.784561404328525239e-03, 1.370585530681736781e-02, + 1.629051763370604339e-02, 1.370585530681736781e-02, 6.784561404328525239e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 6.784561404328525239e-03, 8.064000000000001722e-03, + 6.784561404328525239e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 8.064000000000001722e-03, + 9.584716258668149469e-03, 8.064000000000001722e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 6.784561404328525239e-03, 8.064000000000001722e-03, 6.784561404328525239e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03, 3.358438595671482240e-03, 3.991775919106036428e-03, 3.358438595671482240e-03, + 1.662467052579081651e-03 +}; + +// ---- order 10: 216 point(s), tdim=3 ---- +inline constexpr int hexahedron_o10_npts = 216; +inline constexpr int hexahedron_o10_tdim = 3; +inline constexpr double hexahedron_o10_points[648] = { + 3.376524289842397497e-02, 3.376524289842397497e-02, 3.376524289842397497e-02, 3.376524289842397497e-02, + 3.376524289842397497e-02, 1.693953067668677037e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, + 3.806904069584015060e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.376524289842397497e-02, 3.376524289842397497e-02, 8.306046932331323518e-01, 3.376524289842397497e-02, + 3.376524289842397497e-02, 9.662347571015760250e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, + 3.376524289842397497e-02, 3.376524289842397497e-02, 1.693953067668677037e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, + 1.693953067668677037e-01, 6.193095930415984940e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, + 3.806904069584015060e-01, 1.693953067668677037e-01, 3.376524289842397497e-02, 3.806904069584015060e-01, + 3.806904069584015060e-01, 3.376524289842397497e-02, 3.806904069584015060e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 9.662347571015760250e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.376524289842397497e-02, 3.376524289842397497e-02, 6.193095930415984940e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 6.193095930415984940e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, + 6.193095930415984940e-01, 6.193095930415984940e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, + 8.306046932331323518e-01, 1.693953067668677037e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, + 3.806904069584015060e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, + 8.306046932331323518e-01, 9.662347571015760250e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, + 3.376524289842397497e-02, 3.376524289842397497e-02, 9.662347571015760250e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, + 9.662347571015760250e-01, 6.193095930415984940e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, 9.662347571015760250e-01, + 1.693953067668677037e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 1.693953067668677037e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 1.693953067668677037e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 1.693953067668677037e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 1.693953067668677037e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 1.693953067668677037e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 1.693953067668677037e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 1.693953067668677037e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 1.693953067668677037e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 1.693953067668677037e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 1.693953067668677037e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 3.806904069584015060e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 3.806904069584015060e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.806904069584015060e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 3.806904069584015060e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 3.806904069584015060e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 3.806904069584015060e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 3.806904069584015060e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 3.806904069584015060e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 3.806904069584015060e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 3.806904069584015060e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 3.806904069584015060e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 3.806904069584015060e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 6.193095930415984940e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 6.193095930415984940e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 6.193095930415984940e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 6.193095930415984940e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 6.193095930415984940e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 6.193095930415984940e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 6.193095930415984940e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 6.193095930415984940e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 6.193095930415984940e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 6.193095930415984940e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 6.193095930415984940e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 6.193095930415984940e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 6.193095930415984940e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 6.193095930415984940e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 8.306046932331323518e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 8.306046932331323518e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 8.306046932331323518e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 8.306046932331323518e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 9.662347571015760250e-01, 3.376524289842397497e-02, 3.376524289842397497e-02, 9.662347571015760250e-01, + 3.376524289842397497e-02, 1.693953067668677037e-01, 9.662347571015760250e-01, 3.376524289842397497e-02, + 3.806904069584015060e-01, 9.662347571015760250e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 9.662347571015760250e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 1.693953067668677037e-01, 1.693953067668677037e-01, + 9.662347571015760250e-01, 1.693953067668677037e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 1.693953067668677037e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 9.662347571015760250e-01, 3.806904069584015060e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, + 3.806904069584015060e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 9.662347571015760250e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 6.193095930415984940e-01, 1.693953067668677037e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 6.193095930415984940e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 9.662347571015760250e-01, 8.306046932331323518e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, + 8.306046932331323518e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, 8.306046932331323518e-01, + 3.806904069584015060e-01, 9.662347571015760250e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 9.662347571015760250e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 3.376524289842397497e-02, 9.662347571015760250e-01, 9.662347571015760250e-01, 1.693953067668677037e-01, + 9.662347571015760250e-01, 9.662347571015760250e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 9.662347571015760250e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 9.662347571015760250e-01, 9.662347571015760250e-01, 9.662347571015760250e-01 +}; +inline constexpr double hexahedron_o10_weights[216] = { + 6.285913119545592570e-04, 1.323637895294248364e-03, 1.716781003873725248e-03, 1.716781003873724164e-03, + 1.323637895294248364e-03, 6.285913119545592570e-04, 1.323637895294248364e-03, 2.787212047858594107e-03, + 3.615061728395055352e-03, 3.615061728395053183e-03, 2.787212047858594107e-03, 1.323637895294248364e-03, + 1.716781003873725465e-03, 3.615061728395055785e-03, 4.688796932457027673e-03, 4.688796932457025071e-03, + 3.615061728395055785e-03, 1.716781003873725465e-03, 1.716781003873724164e-03, 3.615061728395053183e-03, + 4.688796932457024204e-03, 4.688796932457021602e-03, 3.615061728395053183e-03, 1.716781003873724164e-03, + 1.323637895294248364e-03, 2.787212047858594107e-03, 3.615061728395055352e-03, 3.615061728395053183e-03, + 2.787212047858594107e-03, 1.323637895294248364e-03, 6.285913119545592570e-04, 1.323637895294248364e-03, + 1.716781003873725248e-03, 1.716781003873724164e-03, 1.323637895294248364e-03, 6.285913119545592570e-04, + 1.323637895294248364e-03, 2.787212047858594107e-03, 3.615061728395055352e-03, 3.615061728395053183e-03, + 2.787212047858594107e-03, 1.323637895294248364e-03, 2.787212047858593673e-03, 5.869090804476496254e-03, + 7.612311221185836217e-03, 7.612311221185831880e-03, 5.869090804476496254e-03, 2.787212047858593673e-03, + 3.615061728395054918e-03, 7.612311221185836217e-03, 9.873297936367590732e-03, 9.873297936367585528e-03, + 7.612311221185836217e-03, 3.615061728395054918e-03, 3.615061728395053183e-03, 7.612311221185832748e-03, + 9.873297936367587263e-03, 9.873297936367580324e-03, 7.612311221185832748e-03, 3.615061728395053183e-03, + 2.787212047858593673e-03, 5.869090804476496254e-03, 7.612311221185836217e-03, 7.612311221185831880e-03, + 5.869090804476496254e-03, 2.787212047858593673e-03, 1.323637895294248364e-03, 2.787212047858594107e-03, + 3.615061728395055352e-03, 3.615061728395053183e-03, 2.787212047858594107e-03, 1.323637895294248364e-03, + 1.716781003873725465e-03, 3.615061728395055785e-03, 4.688796932457027673e-03, 4.688796932457025071e-03, + 3.615061728395055785e-03, 1.716781003873725465e-03, 3.615061728395054918e-03, 7.612311221185836217e-03, + 9.873297936367590732e-03, 9.873297936367585528e-03, 7.612311221185836217e-03, 3.615061728395054918e-03, + 4.688796932457027673e-03, 9.873297936367592467e-03, 1.280583640208747542e-02, 1.280583640208746848e-02, + 9.873297936367592467e-03, 4.688796932457027673e-03, 4.688796932457024204e-03, 9.873297936367585528e-03, + 1.280583640208746675e-02, 1.280583640208745981e-02, 9.873297936367585528e-03, 4.688796932457024204e-03, + 3.615061728395054918e-03, 7.612311221185836217e-03, 9.873297936367590732e-03, 9.873297936367585528e-03, + 7.612311221185836217e-03, 3.615061728395054918e-03, 1.716781003873725465e-03, 3.615061728395055785e-03, + 4.688796932457027673e-03, 4.688796932457025071e-03, 3.615061728395055785e-03, 1.716781003873725465e-03, + 1.716781003873724164e-03, 3.615061728395053183e-03, 4.688796932457024204e-03, 4.688796932457021602e-03, + 3.615061728395053183e-03, 1.716781003873724164e-03, 3.615061728395053183e-03, 7.612311221185832748e-03, + 9.873297936367587263e-03, 9.873297936367580324e-03, 7.612311221185832748e-03, 3.615061728395053183e-03, + 4.688796932457024204e-03, 9.873297936367585528e-03, 1.280583640208746675e-02, 1.280583640208745981e-02, + 9.873297936367585528e-03, 4.688796932457024204e-03, 4.688796932457022469e-03, 9.873297936367580324e-03, + 1.280583640208746155e-02, 1.280583640208745287e-02, 9.873297936367580324e-03, 4.688796932457022469e-03, + 3.615061728395053183e-03, 7.612311221185832748e-03, 9.873297936367587263e-03, 9.873297936367580324e-03, + 7.612311221185832748e-03, 3.615061728395053183e-03, 1.716781003873724164e-03, 3.615061728395053183e-03, + 4.688796932457024204e-03, 4.688796932457021602e-03, 3.615061728395053183e-03, 1.716781003873724164e-03, + 1.323637895294248364e-03, 2.787212047858594107e-03, 3.615061728395055352e-03, 3.615061728395053183e-03, + 2.787212047858594107e-03, 1.323637895294248364e-03, 2.787212047858593673e-03, 5.869090804476496254e-03, + 7.612311221185836217e-03, 7.612311221185831880e-03, 5.869090804476496254e-03, 2.787212047858593673e-03, + 3.615061728395054918e-03, 7.612311221185836217e-03, 9.873297936367590732e-03, 9.873297936367585528e-03, + 7.612311221185836217e-03, 3.615061728395054918e-03, 3.615061728395053183e-03, 7.612311221185832748e-03, + 9.873297936367587263e-03, 9.873297936367580324e-03, 7.612311221185832748e-03, 3.615061728395053183e-03, + 2.787212047858593673e-03, 5.869090804476496254e-03, 7.612311221185836217e-03, 7.612311221185831880e-03, + 5.869090804476496254e-03, 2.787212047858593673e-03, 1.323637895294248364e-03, 2.787212047858594107e-03, + 3.615061728395055352e-03, 3.615061728395053183e-03, 2.787212047858594107e-03, 1.323637895294248364e-03, + 6.285913119545592570e-04, 1.323637895294248364e-03, 1.716781003873725248e-03, 1.716781003873724164e-03, + 1.323637895294248364e-03, 6.285913119545592570e-04, 1.323637895294248364e-03, 2.787212047858594107e-03, + 3.615061728395055352e-03, 3.615061728395053183e-03, 2.787212047858594107e-03, 1.323637895294248364e-03, + 1.716781003873725465e-03, 3.615061728395055785e-03, 4.688796932457027673e-03, 4.688796932457025071e-03, + 3.615061728395055785e-03, 1.716781003873725465e-03, 1.716781003873724164e-03, 3.615061728395053183e-03, + 4.688796932457024204e-03, 4.688796932457021602e-03, 3.615061728395053183e-03, 1.716781003873724164e-03, + 1.323637895294248364e-03, 2.787212047858594107e-03, 3.615061728395055352e-03, 3.615061728395053183e-03, + 2.787212047858594107e-03, 1.323637895294248364e-03, 6.285913119545592570e-04, 1.323637895294248364e-03, + 1.716781003873725248e-03, 1.716781003873724164e-03, 1.323637895294248364e-03, 6.285913119545592570e-04 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_interval.h b/cpp/src/generated/quadrature_tables_interval.h new file mode 100644 index 0000000..3042455 --- /dev/null +++ b/cpp/src/generated/quadrature_tables_interval.h @@ -0,0 +1,122 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'interval', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 2 point(s), tdim=1 ---- +inline constexpr int interval_o1_npts = 2; +inline constexpr int interval_o1_tdim = 1; +inline constexpr double interval_o1_points[2] = { + 0.000000000000000000e+00, 1.000000000000000000e+00 +}; +inline constexpr double interval_o1_weights[2] = { + 4.999999999999998890e-01, 4.999999999999998890e-01 +}; + +// ---- order 2: 3 point(s), tdim=1 ---- +inline constexpr int interval_o2_npts = 3; +inline constexpr int interval_o2_tdim = 1; +inline constexpr double interval_o2_points[3] = { + 1.110223024625156540e-16, 9.999999999999998890e-01, 5.000000000000000000e-01 +}; +inline constexpr double interval_o2_weights[3] = { + 1.666666666666667129e-01, 1.666666666666666574e-01, 6.666666666666662966e-01 +}; + +// ---- order 3: 3 point(s), tdim=1 ---- +inline constexpr int interval_o3_npts = 3; +inline constexpr int interval_o3_tdim = 1; +inline constexpr double interval_o3_points[3] = { + 1.110223024625156540e-16, 9.999999999999998890e-01, 5.000000000000000000e-01 +}; +inline constexpr double interval_o3_weights[3] = { + 1.666666666666667129e-01, 1.666666666666666574e-01, 6.666666666666662966e-01 +}; + +// ---- order 4: 4 point(s), tdim=1 ---- +inline constexpr int interval_o4_npts = 4; +inline constexpr int interval_o4_tdim = 1; +inline constexpr double interval_o4_points[4] = { + -1.110223024625156540e-16, 1.000000000000000000e+00, 2.763932022500210639e-01, 7.236067977499789361e-01 +}; +inline constexpr double interval_o4_weights[4] = { + 8.333333333333323156e-02, 8.333333333333335646e-02, 4.166666666666665186e-01, 4.166666666666663521e-01 +}; + +// ---- order 5: 4 point(s), tdim=1 ---- +inline constexpr int interval_o5_npts = 4; +inline constexpr int interval_o5_tdim = 1; +inline constexpr double interval_o5_points[4] = { + -1.110223024625156540e-16, 1.000000000000000000e+00, 2.763932022500210639e-01, 7.236067977499789361e-01 +}; +inline constexpr double interval_o5_weights[4] = { + 8.333333333333323156e-02, 8.333333333333335646e-02, 4.166666666666665186e-01, 4.166666666666663521e-01 +}; + +// ---- order 6: 5 point(s), tdim=1 ---- +inline constexpr int interval_o6_npts = 5; +inline constexpr int interval_o6_tdim = 1; +inline constexpr double interval_o6_points[5] = { + 5.551115123125782702e-17, 1.000000000000000000e+00, 1.726731646460114566e-01, 5.000000000000000000e-01, + 8.273268353539885434e-01 +}; +inline constexpr double interval_o6_weights[5] = { + 5.000000000000003747e-02, 5.000000000000004441e-02, 2.722222222222222543e-01, 3.555555555555554581e-01, + 2.722222222222220323e-01 +}; + +// ---- order 7: 5 point(s), tdim=1 ---- +inline constexpr int interval_o7_npts = 5; +inline constexpr int interval_o7_tdim = 1; +inline constexpr double interval_o7_points[5] = { + 5.551115123125782702e-17, 1.000000000000000000e+00, 1.726731646460114566e-01, 5.000000000000000000e-01, + 8.273268353539885434e-01 +}; +inline constexpr double interval_o7_weights[5] = { + 5.000000000000003747e-02, 5.000000000000004441e-02, 2.722222222222222543e-01, 3.555555555555554581e-01, + 2.722222222222220323e-01 +}; + +// ---- order 8: 6 point(s), tdim=1 ---- +inline constexpr int interval_o8_npts = 6; +inline constexpr int interval_o8_tdim = 1; +inline constexpr double interval_o8_points[6] = { + 2.775557561562891351e-16, 1.000000000000000000e+00, 1.174723380352676871e-01, 3.573842417596773680e-01, + 6.426157582403224655e-01, 8.825276619647323129e-01 +}; +inline constexpr double interval_o8_weights[6] = { + 3.333333333333327042e-02, 3.333333333333340920e-02, 1.892374781489237490e-01, 2.774291885177430084e-01, + 2.774291885177428418e-01, 1.892374781489237490e-01 +}; + +// ---- order 9: 6 point(s), tdim=1 ---- +inline constexpr int interval_o9_npts = 6; +inline constexpr int interval_o9_tdim = 1; +inline constexpr double interval_o9_points[6] = { + 2.775557561562891351e-16, 1.000000000000000000e+00, 1.174723380352676871e-01, 3.573842417596773680e-01, + 6.426157582403224655e-01, 8.825276619647323129e-01 +}; +inline constexpr double interval_o9_weights[6] = { + 3.333333333333327042e-02, 3.333333333333340920e-02, 1.892374781489237490e-01, 2.774291885177430084e-01, + 2.774291885177428418e-01, 1.892374781489237490e-01 +}; + +// ---- order 10: 7 point(s), tdim=1 ---- +inline constexpr int interval_o10_npts = 7; +inline constexpr int interval_o10_tdim = 1; +inline constexpr double interval_o10_points[7] = { + -1.110223024625156540e-16, 1.000000000000000000e+00, 8.488805186071646247e-02, 2.655756032646428011e-01, + 5.000000000000001110e-01, 7.344243967353571989e-01, 9.151119481392833155e-01 +}; +inline constexpr double interval_o10_weights[7] = { + 2.380952380952375963e-02, 2.380952380952357922e-02, 1.384130236807827041e-01, 2.158726906049313332e-01, + 2.438095238095235040e-01, 2.158726906049308336e-01, 1.384130236807826209e-01 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_prism.h b/cpp/src/generated/quadrature_tables_prism.h new file mode 100644 index 0000000..d486d5f --- /dev/null +++ b/cpp/src/generated/quadrature_tables_prism.h @@ -0,0 +1,762 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'prism', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 1 point(s), tdim=3 ---- +inline constexpr int prism_o1_npts = 1; +inline constexpr int prism_o1_tdim = 3; +inline constexpr double prism_o1_points[3] = { + 3.333333333333333148e-01, 3.333333333333333703e-01, 5.000000000000000000e-01 +}; +inline constexpr double prism_o1_weights[1] = { + 5.000000000000000000e-01 +}; + +// ---- order 2: 8 point(s), tdim=3 ---- +inline constexpr int prism_o2_npts = 8; +inline constexpr int prism_o2_tdim = 3; +inline constexpr double prism_o2_points[24] = { + 1.785587282636164341e-01, 1.550510257216821675e-01, 2.113248654051871345e-01, 1.785587282636164341e-01, + 1.550510257216821675e-01, 7.886751345948128655e-01, 7.503111022260813834e-02, 6.449489742783177659e-01, + 2.113248654051871345e-01, 7.503111022260813834e-02, 6.449489742783177659e-01, 7.886751345948128655e-01, + 6.663902460147014262e-01, 1.550510257216821675e-01, 2.113248654051871345e-01, 6.663902460147014262e-01, + 1.550510257216821675e-01, 7.886751345948128655e-01, 2.800199154990741235e-01, 6.449489742783177659e-01, + 2.113248654051871345e-01, 2.800199154990741235e-01, 6.449489742783177659e-01, 7.886751345948128655e-01 +}; +inline constexpr double prism_o2_weights[8] = { + 7.951034543599429238e-02, 7.951034543599429238e-02, 4.548965456400570762e-02, 4.548965456400570762e-02, + 7.951034543599429238e-02, 7.951034543599429238e-02, 4.548965456400570762e-02, 4.548965456400570762e-02 +}; + +// ---- order 3: 8 point(s), tdim=3 ---- +inline constexpr int prism_o3_npts = 8; +inline constexpr int prism_o3_tdim = 3; +inline constexpr double prism_o3_points[24] = { + 1.785587282636164341e-01, 1.550510257216821675e-01, 2.113248654051871345e-01, 1.785587282636164341e-01, + 1.550510257216821675e-01, 7.886751345948128655e-01, 7.503111022260813834e-02, 6.449489742783177659e-01, + 2.113248654051871345e-01, 7.503111022260813834e-02, 6.449489742783177659e-01, 7.886751345948128655e-01, + 6.663902460147014262e-01, 1.550510257216821675e-01, 2.113248654051871345e-01, 6.663902460147014262e-01, + 1.550510257216821675e-01, 7.886751345948128655e-01, 2.800199154990741235e-01, 6.449489742783177659e-01, + 2.113248654051871345e-01, 2.800199154990741235e-01, 6.449489742783177659e-01, 7.886751345948128655e-01 +}; +inline constexpr double prism_o3_weights[8] = { + 7.951034543599429238e-02, 7.951034543599429238e-02, 4.548965456400570762e-02, 4.548965456400570762e-02, + 7.951034543599429238e-02, 7.951034543599429238e-02, 4.548965456400570762e-02, 4.548965456400570762e-02 +}; + +// ---- order 4: 27 point(s), tdim=3 ---- +inline constexpr int prism_o4_npts = 27; +inline constexpr int prism_o4_tdim = 3; +inline constexpr double prism_o4_points[81] = { + 1.027176548096262604e-01, 8.858795951270398428e-02, 1.127016653792582979e-01, 1.027176548096262604e-01, + 8.858795951270398428e-02, 5.000000000000000000e-01, 1.027176548096262604e-01, 8.858795951270398428e-02, + 8.872983346207417021e-01, 6.655406783916449631e-02, 4.094668644407347124e-01, 1.127016653792582979e-01, + 6.655406783916449631e-02, 4.094668644407347124e-01, 5.000000000000000000e-01, 6.655406783916449631e-02, + 4.094668644407347124e-01, 8.872983346207417021e-01, 2.393113228708061702e-02, 7.876594617608470017e-01, + 1.127016653792582979e-01, 2.393113228708061702e-02, 7.876594617608470017e-01, 5.000000000000000000e-01, + 2.393113228708061702e-02, 7.876594617608470017e-01, 8.872983346207417021e-01, 4.557060202436480356e-01, + 8.858795951270398428e-02, 1.127016653792582979e-01, 4.557060202436480356e-01, 8.858795951270398428e-02, + 5.000000000000000000e-01, 4.557060202436480356e-01, 8.858795951270398428e-02, 8.872983346207417021e-01, + 2.952665677796326715e-01, 4.094668644407347124e-01, 1.127016653792582979e-01, 2.952665677796326715e-01, + 4.094668644407347124e-01, 5.000000000000000000e-01, 2.952665677796326715e-01, 4.094668644407347124e-01, + 8.872983346207417021e-01, 1.061702691195764714e-01, 7.876594617608470017e-01, 1.127016653792582979e-01, + 1.061702691195764714e-01, 7.876594617608470017e-01, 5.000000000000000000e-01, 1.061702691195764714e-01, + 7.876594617608470017e-01, 8.872983346207417021e-01, 8.086943856776698247e-01, 8.858795951270398428e-02, + 1.127016653792582979e-01, 8.086943856776698247e-01, 8.858795951270398428e-02, 5.000000000000000000e-01, + 8.086943856776698247e-01, 8.858795951270398428e-02, 8.872983346207417021e-01, 5.239790677201008329e-01, + 4.094668644407347124e-01, 1.127016653792582979e-01, 5.239790677201008329e-01, 4.094668644407347124e-01, + 5.000000000000000000e-01, 5.239790677201008329e-01, 4.094668644407347124e-01, 8.872983346207417021e-01, + 1.884094059520723397e-01, 7.876594617608470017e-01, 1.127016653792582979e-01, 1.884094059520723397e-01, + 7.876594617608470017e-01, 5.000000000000000000e-01, 1.884094059520723397e-01, 7.876594617608470017e-01, + 8.872983346207417021e-01 +}; +inline constexpr double prism_o4_weights[27] = { + 1.550400568973453463e-02, 2.480640910357526097e-02, 1.550400568973453463e-02, 1.768835697219028891e-02, + 2.830137115550447266e-02, 1.768835697219028891e-02, 5.387884251655403883e-03, 8.620614802648649336e-03, + 5.387884251655403883e-03, 2.480640910357526444e-02, 3.969025456572043697e-02, 2.480640910357526444e-02, + 2.830137115550447266e-02, 4.528219384880716875e-02, 2.830137115550447266e-02, 8.620614802648649336e-03, + 1.379298368423784345e-02, 8.620614802648649336e-03, 1.550400568973453463e-02, 2.480640910357526097e-02, + 1.550400568973453463e-02, 1.768835697219028891e-02, 2.830137115550447266e-02, 1.768835697219028891e-02, + 5.387884251655403883e-03, 8.620614802648649336e-03, 5.387884251655403883e-03 +}; + +// ---- order 5: 27 point(s), tdim=3 ---- +inline constexpr int prism_o5_npts = 27; +inline constexpr int prism_o5_tdim = 3; +inline constexpr double prism_o5_points[81] = { + 1.027176548096262604e-01, 8.858795951270398428e-02, 1.127016653792582979e-01, 1.027176548096262604e-01, + 8.858795951270398428e-02, 5.000000000000000000e-01, 1.027176548096262604e-01, 8.858795951270398428e-02, + 8.872983346207417021e-01, 6.655406783916449631e-02, 4.094668644407347124e-01, 1.127016653792582979e-01, + 6.655406783916449631e-02, 4.094668644407347124e-01, 5.000000000000000000e-01, 6.655406783916449631e-02, + 4.094668644407347124e-01, 8.872983346207417021e-01, 2.393113228708061702e-02, 7.876594617608470017e-01, + 1.127016653792582979e-01, 2.393113228708061702e-02, 7.876594617608470017e-01, 5.000000000000000000e-01, + 2.393113228708061702e-02, 7.876594617608470017e-01, 8.872983346207417021e-01, 4.557060202436480356e-01, + 8.858795951270398428e-02, 1.127016653792582979e-01, 4.557060202436480356e-01, 8.858795951270398428e-02, + 5.000000000000000000e-01, 4.557060202436480356e-01, 8.858795951270398428e-02, 8.872983346207417021e-01, + 2.952665677796326715e-01, 4.094668644407347124e-01, 1.127016653792582979e-01, 2.952665677796326715e-01, + 4.094668644407347124e-01, 5.000000000000000000e-01, 2.952665677796326715e-01, 4.094668644407347124e-01, + 8.872983346207417021e-01, 1.061702691195764714e-01, 7.876594617608470017e-01, 1.127016653792582979e-01, + 1.061702691195764714e-01, 7.876594617608470017e-01, 5.000000000000000000e-01, 1.061702691195764714e-01, + 7.876594617608470017e-01, 8.872983346207417021e-01, 8.086943856776698247e-01, 8.858795951270398428e-02, + 1.127016653792582979e-01, 8.086943856776698247e-01, 8.858795951270398428e-02, 5.000000000000000000e-01, + 8.086943856776698247e-01, 8.858795951270398428e-02, 8.872983346207417021e-01, 5.239790677201008329e-01, + 4.094668644407347124e-01, 1.127016653792582979e-01, 5.239790677201008329e-01, 4.094668644407347124e-01, + 5.000000000000000000e-01, 5.239790677201008329e-01, 4.094668644407347124e-01, 8.872983346207417021e-01, + 1.884094059520723397e-01, 7.876594617608470017e-01, 1.127016653792582979e-01, 1.884094059520723397e-01, + 7.876594617608470017e-01, 5.000000000000000000e-01, 1.884094059520723397e-01, 7.876594617608470017e-01, + 8.872983346207417021e-01 +}; +inline constexpr double prism_o5_weights[27] = { + 1.550400568973453463e-02, 2.480640910357526097e-02, 1.550400568973453463e-02, 1.768835697219028891e-02, + 2.830137115550447266e-02, 1.768835697219028891e-02, 5.387884251655403883e-03, 8.620614802648649336e-03, + 5.387884251655403883e-03, 2.480640910357526444e-02, 3.969025456572043697e-02, 2.480640910357526444e-02, + 2.830137115550447266e-02, 4.528219384880716875e-02, 2.830137115550447266e-02, 8.620614802648649336e-03, + 1.379298368423784345e-02, 8.620614802648649336e-03, 1.550400568973453463e-02, 2.480640910357526097e-02, + 1.550400568973453463e-02, 1.768835697219028891e-02, 2.830137115550447266e-02, 1.768835697219028891e-02, + 5.387884251655403883e-03, 8.620614802648649336e-03, 5.387884251655403883e-03 +}; + +// ---- order 6: 64 point(s), tdim=3 ---- +inline constexpr int prism_o6_npts = 64; +inline constexpr int prism_o6_tdim = 3; +inline constexpr double prism_o6_points[192] = { + 6.546699455501446552e-02, 5.710419611451766908e-02, 6.943184420297371373e-02, 6.546699455501446552e-02, + 5.710419611451766908e-02, 3.300094782075718713e-01, 6.546699455501446552e-02, 5.710419611451766908e-02, + 6.699905217924281287e-01, 6.546699455501446552e-02, 5.710419611451766908e-02, 9.305681557970262308e-01, + 5.021012321136977818e-02, 2.768430136381238027e-01, 6.943184420297371373e-02, 5.021012321136977818e-02, + 2.768430136381238027e-01, 3.300094782075718713e-01, 5.021012321136977818e-02, 2.768430136381238027e-01, + 6.699905217924281287e-01, 5.021012321136977818e-02, 2.768430136381238027e-01, 9.305681557970262308e-01, + 2.891208422438901193e-02, 5.835904323689168338e-01, 6.943184420297371373e-02, 2.891208422438901193e-02, + 5.835904323689168338e-01, 3.300094782075718713e-01, 2.891208422438901193e-02, 5.835904323689168338e-01, + 6.699905217924281287e-01, 2.891208422438901193e-02, 5.835904323689168338e-01, 9.305681557970262308e-01, + 9.703785126946112838e-03, 8.602401356562194845e-01, 6.943184420297371373e-02, 9.703785126946112838e-03, + 8.602401356562194845e-01, 3.300094782075718713e-01, 9.703785126946112838e-03, 8.602401356562194845e-01, + 6.699905217924281287e-01, 9.703785126946112838e-03, 8.602401356562194845e-01, 9.305681557970262308e-01, + 3.111645522443570178e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 3.111645522443570178e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 3.111645522443570178e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 3.111645522443570178e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 2.386486597314429192e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 2.386486597314429192e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 2.386486597314429192e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 2.386486597314429192e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 1.374191041345743658e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 1.374191041345743658e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 1.374191041345743658e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 1.374191041345743658e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 4.612207990645205563e-02, 8.602401356562194845e-01, 6.943184420297371373e-02, 4.612207990645205563e-02, + 8.602401356562194845e-01, 3.300094782075718713e-01, 4.612207990645205563e-02, 8.602401356562194845e-01, + 6.699905217924281287e-01, 4.612207990645205563e-02, 8.602401356562194845e-01, 9.305681557970262308e-01, + 6.317312516411253132e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 6.317312516411253132e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 6.317312516411253132e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 6.317312516411253132e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 4.845083266304332503e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 4.845083266304332503e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 4.845083266304332503e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 4.845083266304332503e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 2.789904634965088004e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 2.789904634965088004e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 2.789904634965088004e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 2.789904634965088004e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 9.363778443732852230e-02, 8.602401356562194845e-01, 6.943184420297371373e-02, 9.363778443732852230e-02, + 8.602401356562194845e-01, 3.300094782075718713e-01, 9.363778443732852230e-02, 8.602401356562194845e-01, + 6.699905217924281287e-01, 9.363778443732852230e-02, 8.602401356562194845e-01, 9.305681557970262308e-01, + 8.774288093304678515e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 8.774288093304678515e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 8.774288093304678515e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 8.774288093304678515e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 6.729468631505063358e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 6.729468631505063358e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 6.729468631505063358e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 6.729468631505063358e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 3.874974834066941543e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 3.874974834066941543e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 3.874974834066941543e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 3.874974834066941543e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 1.300560792168344582e-01, 8.602401356562194845e-01, 6.943184420297371373e-02, 1.300560792168344582e-01, + 8.602401356562194845e-01, 3.300094782075718713e-01, 1.300560792168344582e-01, 8.602401356562194845e-01, + 6.699905217924281287e-01, 1.300560792168344582e-01, 8.602401356562194845e-01, 9.305681557970262308e-01 +}; +inline constexpr double prism_o6_weights[64] = { + 4.099185534025746348e-03, 7.684998562665407498e-03, 7.684998562665404896e-03, 4.099185534025746348e-03, + 6.154955439201190322e-03, 1.153907850984177766e-02, 1.153907850984177419e-02, 6.154955439201190322e-03, + 3.927985482847706419e-03, 7.364039158337254941e-03, 7.364039158337252339e-03, 3.927985482847706419e-03, + 9.432477046255943395e-04, 1.768365250637032654e-03, 1.768365250637032003e-03, 9.432477046255943395e-04, + 7.684998562665406631e-03, 1.440754569851544417e-02, 1.440754569851543897e-02, 7.684998562665406631e-03, + 1.153907850984177939e-02, 2.163302954368310355e-02, 2.163302954368309314e-02, 1.153907850984177939e-02, + 7.364039158337254941e-03, 1.380582310253589302e-02, 1.380582310253588782e-02, 7.364039158337254941e-03, + 1.768365250637032654e-03, 3.315264531602363316e-03, 3.315264531602362015e-03, 1.768365250637032654e-03, + 7.684998562665404029e-03, 1.440754569851543897e-02, 1.440754569851543550e-02, 7.684998562665404029e-03, + 1.153907850984177419e-02, 2.163302954368309314e-02, 2.163302954368308620e-02, 1.153907850984177419e-02, + 7.364039158337252339e-03, 1.380582310253588955e-02, 1.380582310253588435e-02, 7.364039158337252339e-03, + 1.768365250637032003e-03, 3.315264531602362015e-03, 3.315264531602361148e-03, 1.768365250637032003e-03, + 4.099185534025746348e-03, 7.684998562665407498e-03, 7.684998562665404896e-03, 4.099185534025746348e-03, + 6.154955439201190322e-03, 1.153907850984177766e-02, 1.153907850984177419e-02, 6.154955439201190322e-03, + 3.927985482847706419e-03, 7.364039158337254941e-03, 7.364039158337252339e-03, 3.927985482847706419e-03, + 9.432477046255943395e-04, 1.768365250637032654e-03, 1.768365250637032003e-03, 9.432477046255943395e-04 +}; + +// ---- order 7: 64 point(s), tdim=3 ---- +inline constexpr int prism_o7_npts = 64; +inline constexpr int prism_o7_tdim = 3; +inline constexpr double prism_o7_points[192] = { + 6.546699455501446552e-02, 5.710419611451766908e-02, 6.943184420297371373e-02, 6.546699455501446552e-02, + 5.710419611451766908e-02, 3.300094782075718713e-01, 6.546699455501446552e-02, 5.710419611451766908e-02, + 6.699905217924281287e-01, 6.546699455501446552e-02, 5.710419611451766908e-02, 9.305681557970262308e-01, + 5.021012321136977818e-02, 2.768430136381238027e-01, 6.943184420297371373e-02, 5.021012321136977818e-02, + 2.768430136381238027e-01, 3.300094782075718713e-01, 5.021012321136977818e-02, 2.768430136381238027e-01, + 6.699905217924281287e-01, 5.021012321136977818e-02, 2.768430136381238027e-01, 9.305681557970262308e-01, + 2.891208422438901193e-02, 5.835904323689168338e-01, 6.943184420297371373e-02, 2.891208422438901193e-02, + 5.835904323689168338e-01, 3.300094782075718713e-01, 2.891208422438901193e-02, 5.835904323689168338e-01, + 6.699905217924281287e-01, 2.891208422438901193e-02, 5.835904323689168338e-01, 9.305681557970262308e-01, + 9.703785126946112838e-03, 8.602401356562194845e-01, 6.943184420297371373e-02, 9.703785126946112838e-03, + 8.602401356562194845e-01, 3.300094782075718713e-01, 9.703785126946112838e-03, 8.602401356562194845e-01, + 6.699905217924281287e-01, 9.703785126946112838e-03, 8.602401356562194845e-01, 9.305681557970262308e-01, + 3.111645522443570178e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 3.111645522443570178e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 3.111645522443570178e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 3.111645522443570178e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 2.386486597314429192e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 2.386486597314429192e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 2.386486597314429192e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 2.386486597314429192e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 1.374191041345743658e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 1.374191041345743658e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 1.374191041345743658e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 1.374191041345743658e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 4.612207990645205563e-02, 8.602401356562194845e-01, 6.943184420297371373e-02, 4.612207990645205563e-02, + 8.602401356562194845e-01, 3.300094782075718713e-01, 4.612207990645205563e-02, 8.602401356562194845e-01, + 6.699905217924281287e-01, 4.612207990645205563e-02, 8.602401356562194845e-01, 9.305681557970262308e-01, + 6.317312516411253132e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 6.317312516411253132e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 6.317312516411253132e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 6.317312516411253132e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 4.845083266304332503e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 4.845083266304332503e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 4.845083266304332503e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 4.845083266304332503e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 2.789904634965088004e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 2.789904634965088004e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 2.789904634965088004e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 2.789904634965088004e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 9.363778443732852230e-02, 8.602401356562194845e-01, 6.943184420297371373e-02, 9.363778443732852230e-02, + 8.602401356562194845e-01, 3.300094782075718713e-01, 9.363778443732852230e-02, 8.602401356562194845e-01, + 6.699905217924281287e-01, 9.363778443732852230e-02, 8.602401356562194845e-01, 9.305681557970262308e-01, + 8.774288093304678515e-01, 5.710419611451766908e-02, 6.943184420297371373e-02, 8.774288093304678515e-01, + 5.710419611451766908e-02, 3.300094782075718713e-01, 8.774288093304678515e-01, 5.710419611451766908e-02, + 6.699905217924281287e-01, 8.774288093304678515e-01, 5.710419611451766908e-02, 9.305681557970262308e-01, + 6.729468631505063358e-01, 2.768430136381238027e-01, 6.943184420297371373e-02, 6.729468631505063358e-01, + 2.768430136381238027e-01, 3.300094782075718713e-01, 6.729468631505063358e-01, 2.768430136381238027e-01, + 6.699905217924281287e-01, 6.729468631505063358e-01, 2.768430136381238027e-01, 9.305681557970262308e-01, + 3.874974834066941543e-01, 5.835904323689168338e-01, 6.943184420297371373e-02, 3.874974834066941543e-01, + 5.835904323689168338e-01, 3.300094782075718713e-01, 3.874974834066941543e-01, 5.835904323689168338e-01, + 6.699905217924281287e-01, 3.874974834066941543e-01, 5.835904323689168338e-01, 9.305681557970262308e-01, + 1.300560792168344582e-01, 8.602401356562194845e-01, 6.943184420297371373e-02, 1.300560792168344582e-01, + 8.602401356562194845e-01, 3.300094782075718713e-01, 1.300560792168344582e-01, 8.602401356562194845e-01, + 6.699905217924281287e-01, 1.300560792168344582e-01, 8.602401356562194845e-01, 9.305681557970262308e-01 +}; +inline constexpr double prism_o7_weights[64] = { + 4.099185534025746348e-03, 7.684998562665407498e-03, 7.684998562665404896e-03, 4.099185534025746348e-03, + 6.154955439201190322e-03, 1.153907850984177766e-02, 1.153907850984177419e-02, 6.154955439201190322e-03, + 3.927985482847706419e-03, 7.364039158337254941e-03, 7.364039158337252339e-03, 3.927985482847706419e-03, + 9.432477046255943395e-04, 1.768365250637032654e-03, 1.768365250637032003e-03, 9.432477046255943395e-04, + 7.684998562665406631e-03, 1.440754569851544417e-02, 1.440754569851543897e-02, 7.684998562665406631e-03, + 1.153907850984177939e-02, 2.163302954368310355e-02, 2.163302954368309314e-02, 1.153907850984177939e-02, + 7.364039158337254941e-03, 1.380582310253589302e-02, 1.380582310253588782e-02, 7.364039158337254941e-03, + 1.768365250637032654e-03, 3.315264531602363316e-03, 3.315264531602362015e-03, 1.768365250637032654e-03, + 7.684998562665404029e-03, 1.440754569851543897e-02, 1.440754569851543550e-02, 7.684998562665404029e-03, + 1.153907850984177419e-02, 2.163302954368309314e-02, 2.163302954368308620e-02, 1.153907850984177419e-02, + 7.364039158337252339e-03, 1.380582310253588955e-02, 1.380582310253588435e-02, 7.364039158337252339e-03, + 1.768365250637032003e-03, 3.315264531602362015e-03, 3.315264531602361148e-03, 1.768365250637032003e-03, + 4.099185534025746348e-03, 7.684998562665407498e-03, 7.684998562665404896e-03, 4.099185534025746348e-03, + 6.154955439201190322e-03, 1.153907850984177766e-02, 1.153907850984177419e-02, 6.154955439201190322e-03, + 3.927985482847706419e-03, 7.364039158337254941e-03, 7.364039158337252339e-03, 3.927985482847706419e-03, + 9.432477046255943395e-04, 1.768365250637032654e-03, 1.768365250637032003e-03, 9.432477046255943395e-04 +}; + +// ---- order 8: 125 point(s), tdim=3 ---- +inline constexpr int prism_o8_npts = 125; +inline constexpr int prism_o8_tdim = 3; +inline constexpr double prism_o8_points[375] = { + 4.504259356980373774e-02, 3.980985705146872222e-02, 4.691007703066801815e-02, 4.504259356980373774e-02, + 3.980985705146872222e-02, 2.307653449471584461e-01, 4.504259356980373774e-02, 3.980985705146872222e-02, + 5.000000000000000000e-01, 4.504259356980373774e-02, 3.980985705146872222e-02, 7.692346550528414983e-01, + 4.504259356980373774e-02, 3.980985705146872222e-02, 9.530899229693319263e-01, 3.762125234511120436e-02, + 1.980134178736082107e-01, 4.691007703066801815e-02, 3.762125234511120436e-02, 1.980134178736082107e-01, + 2.307653449471584461e-01, 3.762125234511120436e-02, 1.980134178736082107e-01, 5.000000000000000000e-01, + 3.762125234511120436e-02, 1.980134178736082107e-01, 7.692346550528414983e-01, 3.762125234511120436e-02, + 1.980134178736082107e-01, 9.530899229693319263e-01, 2.636464494447092466e-02, 4.379748102473861593e-01, + 4.691007703066801815e-02, 2.636464494447092466e-02, 4.379748102473861593e-01, 2.307653449471584461e-01, + 2.636464494447092466e-02, 4.379748102473861593e-01, 5.000000000000000000e-01, 2.636464494447092466e-02, + 4.379748102473861593e-01, 7.692346550528414983e-01, 2.636464494447092466e-02, 4.379748102473861593e-01, + 9.530899229693319263e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, 4.691007703066801815e-02, + 1.428579439557139084e-02, 6.954642733536360311e-01, 2.307653449471584461e-01, 1.428579439557139084e-02, + 6.954642733536360311e-01, 5.000000000000000000e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, + 7.692346550528414983e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, 9.530899229693319263e-01, + 4.622288465046429755e-03, 9.014649142011735838e-01, 4.691007703066801815e-02, 4.622288465046429755e-03, + 9.014649142011735838e-01, 2.307653449471584461e-01, 4.622288465046429755e-03, 9.014649142011735838e-01, + 5.000000000000000000e-01, 4.622288465046429755e-03, 9.014649142011735838e-01, 7.692346550528414983e-01, + 4.622288465046429755e-03, 9.014649142011735838e-01, 9.530899229693319263e-01, 2.215786095523792076e-01, + 3.980985705146872222e-02, 4.691007703066801815e-02, 2.215786095523792076e-01, 3.980985705146872222e-02, + 2.307653449471584461e-01, 2.215786095523792076e-01, 3.980985705146872222e-02, 5.000000000000000000e-01, + 2.215786095523792076e-01, 3.980985705146872222e-02, 7.692346550528414983e-01, 2.215786095523792076e-01, + 3.980985705146872222e-02, 9.530899229693319263e-01, 1.850707102673894433e-01, 1.980134178736082107e-01, + 4.691007703066801815e-02, 1.850707102673894433e-01, 1.980134178736082107e-01, 2.307653449471584461e-01, + 1.850707102673894433e-01, 1.980134178736082107e-01, 5.000000000000000000e-01, 1.850707102673894433e-01, + 1.980134178736082107e-01, 7.692346550528414983e-01, 1.850707102673894433e-01, 1.980134178736082107e-01, + 9.530899229693319263e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, 4.691007703066801815e-02, + 1.296959367822541065e-01, 4.379748102473861593e-01, 2.307653449471584461e-01, 1.296959367822541065e-01, + 4.379748102473861593e-01, 5.000000000000000000e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, + 7.692346550528414983e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, 9.530899229693319263e-01, + 7.027629200828172662e-02, 6.954642733536360311e-01, 4.691007703066801815e-02, 7.027629200828172662e-02, + 6.954642733536360311e-01, 2.307653449471584461e-01, 7.027629200828172662e-02, 6.954642733536360311e-01, + 5.000000000000000000e-01, 7.027629200828172662e-02, 6.954642733536360311e-01, 7.692346550528414983e-01, + 7.027629200828172662e-02, 6.954642733536360311e-01, 9.530899229693319263e-01, 2.273848306376403255e-02, + 9.014649142011735838e-01, 4.691007703066801815e-02, 2.273848306376403255e-02, 9.014649142011735838e-01, + 2.307653449471584461e-01, 2.273848306376403255e-02, 9.014649142011735838e-01, 5.000000000000000000e-01, + 2.273848306376403255e-02, 9.014649142011735838e-01, 7.692346550528414983e-01, 2.273848306376403255e-02, + 9.014649142011735838e-01, 9.530899229693319263e-01, 4.800950714742656666e-01, 3.980985705146872222e-02, + 4.691007703066801815e-02, 4.800950714742656666e-01, 3.980985705146872222e-02, 2.307653449471584461e-01, + 4.800950714742656666e-01, 3.980985705146872222e-02, 5.000000000000000000e-01, 4.800950714742656666e-01, + 3.980985705146872222e-02, 7.692346550528414983e-01, 4.800950714742656666e-01, 3.980985705146872222e-02, + 9.530899229693319263e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, 4.691007703066801815e-02, + 4.009932910631959224e-01, 1.980134178736082107e-01, 2.307653449471584461e-01, 4.009932910631959224e-01, + 1.980134178736082107e-01, 5.000000000000000000e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, + 7.692346550528414983e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, 9.530899229693319263e-01, + 2.810125948763069204e-01, 4.379748102473861593e-01, 4.691007703066801815e-02, 2.810125948763069204e-01, + 4.379748102473861593e-01, 2.307653449471584461e-01, 2.810125948763069204e-01, 4.379748102473861593e-01, + 5.000000000000000000e-01, 2.810125948763069204e-01, 4.379748102473861593e-01, 7.692346550528414983e-01, + 2.810125948763069204e-01, 4.379748102473861593e-01, 9.530899229693319263e-01, 1.522678633231819567e-01, + 6.954642733536360311e-01, 4.691007703066801815e-02, 1.522678633231819567e-01, 6.954642733536360311e-01, + 2.307653449471584461e-01, 1.522678633231819567e-01, 6.954642733536360311e-01, 5.000000000000000000e-01, + 1.522678633231819567e-01, 6.954642733536360311e-01, 7.692346550528414983e-01, 1.522678633231819567e-01, + 6.954642733536360311e-01, 9.530899229693319263e-01, 4.926754289941320808e-02, 9.014649142011735838e-01, + 4.691007703066801815e-02, 4.926754289941320808e-02, 9.014649142011735838e-01, 2.307653449471584461e-01, + 4.926754289941320808e-02, 9.014649142011735838e-01, 5.000000000000000000e-01, 4.926754289941320808e-02, + 9.014649142011735838e-01, 7.692346550528414983e-01, 4.926754289941320808e-02, 9.014649142011735838e-01, + 9.530899229693319263e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, 4.691007703066801815e-02, + 7.386115333961520424e-01, 3.980985705146872222e-02, 2.307653449471584461e-01, 7.386115333961520424e-01, + 3.980985705146872222e-02, 5.000000000000000000e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, + 7.692346550528414983e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, 9.530899229693319263e-01, + 6.169158718590023183e-01, 1.980134178736082107e-01, 4.691007703066801815e-02, 6.169158718590023183e-01, + 1.980134178736082107e-01, 2.307653449471584461e-01, 6.169158718590023183e-01, 1.980134178736082107e-01, + 5.000000000000000000e-01, 6.169158718590023183e-01, 1.980134178736082107e-01, 7.692346550528414983e-01, + 6.169158718590023183e-01, 1.980134178736082107e-01, 9.530899229693319263e-01, 4.323292529703596787e-01, + 4.379748102473861593e-01, 4.691007703066801815e-02, 4.323292529703596787e-01, 4.379748102473861593e-01, + 2.307653449471584461e-01, 4.323292529703596787e-01, 4.379748102473861593e-01, 5.000000000000000000e-01, + 4.323292529703596787e-01, 4.379748102473861593e-01, 7.692346550528414983e-01, 4.323292529703596787e-01, + 4.379748102473861593e-01, 9.530899229693319263e-01, 2.342594346380821868e-01, 6.954642733536360311e-01, + 4.691007703066801815e-02, 2.342594346380821868e-01, 6.954642733536360311e-01, 2.307653449471584461e-01, + 2.342594346380821868e-01, 6.954642733536360311e-01, 5.000000000000000000e-01, 2.342594346380821868e-01, + 6.954642733536360311e-01, 7.692346550528414983e-01, 2.342594346380821868e-01, 6.954642733536360311e-01, + 9.530899229693319263e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, 4.691007703066801815e-02, + 7.579660273506237667e-02, 9.014649142011735838e-01, 2.307653449471584461e-01, 7.579660273506237667e-02, + 9.014649142011735838e-01, 5.000000000000000000e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, + 7.692346550528414983e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, 9.530899229693319263e-01, + 9.151475493787275539e-01, 3.980985705146872222e-02, 4.691007703066801815e-02, 9.151475493787275539e-01, + 3.980985705146872222e-02, 2.307653449471584461e-01, 9.151475493787275539e-01, 3.980985705146872222e-02, + 5.000000000000000000e-01, 9.151475493787275539e-01, 3.980985705146872222e-02, 7.692346550528414983e-01, + 9.151475493787275539e-01, 3.980985705146872222e-02, 9.530899229693319263e-01, 7.643653297812805780e-01, + 1.980134178736082107e-01, 4.691007703066801815e-02, 7.643653297812805780e-01, 1.980134178736082107e-01, + 2.307653449471584461e-01, 7.643653297812805780e-01, 1.980134178736082107e-01, 5.000000000000000000e-01, + 7.643653297812805780e-01, 1.980134178736082107e-01, 7.692346550528414983e-01, 7.643653297812805780e-01, + 1.980134178736082107e-01, 9.530899229693319263e-01, 5.356605448081428467e-01, 4.379748102473861593e-01, + 4.691007703066801815e-02, 5.356605448081428467e-01, 4.379748102473861593e-01, 2.307653449471584461e-01, + 5.356605448081428467e-01, 4.379748102473861593e-01, 5.000000000000000000e-01, 5.356605448081428467e-01, + 4.379748102473861593e-01, 7.692346550528414983e-01, 5.356605448081428467e-01, 4.379748102473861593e-01, + 9.530899229693319263e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, 4.691007703066801815e-02, + 2.902499322507924862e-01, 6.954642733536360311e-01, 2.307653449471584461e-01, 2.902499322507924862e-01, + 6.954642733536360311e-01, 5.000000000000000000e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, + 7.692346550528414983e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, 9.530899229693319263e-01, + 9.391279733377998207e-02, 9.014649142011735838e-01, 4.691007703066801815e-02, 9.391279733377998207e-02, + 9.014649142011735838e-01, 2.307653449471584461e-01, 9.391279733377998207e-02, 9.014649142011735838e-01, + 5.000000000000000000e-01, 9.391279733377998207e-02, 9.014649142011735838e-01, 7.692346550528414983e-01, + 9.391279733377998207e-02, 9.014649142011735838e-01, 9.530899229693319263e-01 +}; +inline constexpr double prism_o8_weights[125] = { + 1.358192887310869131e-03, 2.743758082925574181e-03, 3.261178411119655240e-03, 2.743758082925574181e-03, + 1.358192887310869131e-03, 2.346059863934899480e-03, 4.739400989975380701e-03, 5.633161424226804104e-03, + 4.739400989975380701e-03, 2.346059863934899480e-03, 2.054334550482672574e-03, 4.150071083850390141e-03, + 4.932695162699575996e-03, 4.150071083850390141e-03, 2.054334550482672574e-03, 1.037206574171043762e-03, + 2.095316466558683026e-03, 2.490453100704378107e-03, 2.095316466558683026e-03, 1.037206574171043762e-03, + 2.209997319040949370e-04, 4.464533766899758996e-04, 5.306459496896958608e-04, 4.464533766899758996e-04, + 2.209997319040949370e-04, 2.743758082925574181e-03, 5.542812429628291536e-03, 6.588080904390647939e-03, + 5.542812429628291536e-03, 2.743758082925574181e-03, 4.739400989975380701e-03, 9.574317385962028554e-03, + 1.137985063428563655e-02, 9.574317385962028554e-03, 4.739400989975380701e-03, 4.150071083850391009e-03, + 8.383780527355944034e-03, 9.964801280959069071e-03, 8.383780527355944034e-03, 4.150071083850391009e-03, + 2.095316466558683026e-03, 4.232860844080959389e-03, 5.031097489204185841e-03, 4.232860844080959389e-03, + 2.095316466558683026e-03, 4.464533766899758996e-04, 9.019043409716836438e-04, 1.071986260004300241e-03, + 9.019043409716836438e-04, 4.464533766899758996e-04, 3.261178411119655240e-03, 6.588080904390647939e-03, + 7.830467033449181602e-03, 6.588080904390647939e-03, 3.261178411119655240e-03, 5.633161424226804104e-03, + 1.137985063428563655e-02, 1.352587294092913797e-02, 1.137985063428563655e-02, 5.633161424226804104e-03, + 4.932695162699575996e-03, 9.964801280959069071e-03, 1.184397232787767415e-02, 9.964801280959069071e-03, + 4.932695162699575996e-03, 2.490453100704377674e-03, 5.031097489204184973e-03, 5.979866307504941997e-03, + 5.031097489204184973e-03, 2.490453100704377674e-03, 5.306459496896957524e-04, 1.071986260004300241e-03, + 1.274142377893362679e-03, 1.071986260004300241e-03, 5.306459496896957524e-04, 2.743758082925574181e-03, + 5.542812429628291536e-03, 6.588080904390647939e-03, 5.542812429628291536e-03, 2.743758082925574181e-03, + 4.739400989975380701e-03, 9.574317385962028554e-03, 1.137985063428563655e-02, 9.574317385962028554e-03, + 4.739400989975380701e-03, 4.150071083850391009e-03, 8.383780527355944034e-03, 9.964801280959069071e-03, + 8.383780527355944034e-03, 4.150071083850391009e-03, 2.095316466558683026e-03, 4.232860844080959389e-03, + 5.031097489204185841e-03, 4.232860844080959389e-03, 2.095316466558683026e-03, 4.464533766899758996e-04, + 9.019043409716836438e-04, 1.071986260004300241e-03, 9.019043409716836438e-04, 4.464533766899758996e-04, + 1.358192887310869131e-03, 2.743758082925574181e-03, 3.261178411119655240e-03, 2.743758082925574181e-03, + 1.358192887310869131e-03, 2.346059863934899480e-03, 4.739400989975380701e-03, 5.633161424226804104e-03, + 4.739400989975380701e-03, 2.346059863934899480e-03, 2.054334550482672574e-03, 4.150071083850390141e-03, + 4.932695162699575996e-03, 4.150071083850390141e-03, 2.054334550482672574e-03, 1.037206574171043762e-03, + 2.095316466558683026e-03, 2.490453100704378107e-03, 2.095316466558683026e-03, 1.037206574171043762e-03, + 2.209997319040949370e-04, 4.464533766899758996e-04, 5.306459496896958608e-04, 4.464533766899758996e-04, + 2.209997319040949370e-04 +}; + +// ---- order 9: 125 point(s), tdim=3 ---- +inline constexpr int prism_o9_npts = 125; +inline constexpr int prism_o9_tdim = 3; +inline constexpr double prism_o9_points[375] = { + 4.504259356980373774e-02, 3.980985705146872222e-02, 4.691007703066801815e-02, 4.504259356980373774e-02, + 3.980985705146872222e-02, 2.307653449471584461e-01, 4.504259356980373774e-02, 3.980985705146872222e-02, + 5.000000000000000000e-01, 4.504259356980373774e-02, 3.980985705146872222e-02, 7.692346550528414983e-01, + 4.504259356980373774e-02, 3.980985705146872222e-02, 9.530899229693319263e-01, 3.762125234511120436e-02, + 1.980134178736082107e-01, 4.691007703066801815e-02, 3.762125234511120436e-02, 1.980134178736082107e-01, + 2.307653449471584461e-01, 3.762125234511120436e-02, 1.980134178736082107e-01, 5.000000000000000000e-01, + 3.762125234511120436e-02, 1.980134178736082107e-01, 7.692346550528414983e-01, 3.762125234511120436e-02, + 1.980134178736082107e-01, 9.530899229693319263e-01, 2.636464494447092466e-02, 4.379748102473861593e-01, + 4.691007703066801815e-02, 2.636464494447092466e-02, 4.379748102473861593e-01, 2.307653449471584461e-01, + 2.636464494447092466e-02, 4.379748102473861593e-01, 5.000000000000000000e-01, 2.636464494447092466e-02, + 4.379748102473861593e-01, 7.692346550528414983e-01, 2.636464494447092466e-02, 4.379748102473861593e-01, + 9.530899229693319263e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, 4.691007703066801815e-02, + 1.428579439557139084e-02, 6.954642733536360311e-01, 2.307653449471584461e-01, 1.428579439557139084e-02, + 6.954642733536360311e-01, 5.000000000000000000e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, + 7.692346550528414983e-01, 1.428579439557139084e-02, 6.954642733536360311e-01, 9.530899229693319263e-01, + 4.622288465046429755e-03, 9.014649142011735838e-01, 4.691007703066801815e-02, 4.622288465046429755e-03, + 9.014649142011735838e-01, 2.307653449471584461e-01, 4.622288465046429755e-03, 9.014649142011735838e-01, + 5.000000000000000000e-01, 4.622288465046429755e-03, 9.014649142011735838e-01, 7.692346550528414983e-01, + 4.622288465046429755e-03, 9.014649142011735838e-01, 9.530899229693319263e-01, 2.215786095523792076e-01, + 3.980985705146872222e-02, 4.691007703066801815e-02, 2.215786095523792076e-01, 3.980985705146872222e-02, + 2.307653449471584461e-01, 2.215786095523792076e-01, 3.980985705146872222e-02, 5.000000000000000000e-01, + 2.215786095523792076e-01, 3.980985705146872222e-02, 7.692346550528414983e-01, 2.215786095523792076e-01, + 3.980985705146872222e-02, 9.530899229693319263e-01, 1.850707102673894433e-01, 1.980134178736082107e-01, + 4.691007703066801815e-02, 1.850707102673894433e-01, 1.980134178736082107e-01, 2.307653449471584461e-01, + 1.850707102673894433e-01, 1.980134178736082107e-01, 5.000000000000000000e-01, 1.850707102673894433e-01, + 1.980134178736082107e-01, 7.692346550528414983e-01, 1.850707102673894433e-01, 1.980134178736082107e-01, + 9.530899229693319263e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, 4.691007703066801815e-02, + 1.296959367822541065e-01, 4.379748102473861593e-01, 2.307653449471584461e-01, 1.296959367822541065e-01, + 4.379748102473861593e-01, 5.000000000000000000e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, + 7.692346550528414983e-01, 1.296959367822541065e-01, 4.379748102473861593e-01, 9.530899229693319263e-01, + 7.027629200828172662e-02, 6.954642733536360311e-01, 4.691007703066801815e-02, 7.027629200828172662e-02, + 6.954642733536360311e-01, 2.307653449471584461e-01, 7.027629200828172662e-02, 6.954642733536360311e-01, + 5.000000000000000000e-01, 7.027629200828172662e-02, 6.954642733536360311e-01, 7.692346550528414983e-01, + 7.027629200828172662e-02, 6.954642733536360311e-01, 9.530899229693319263e-01, 2.273848306376403255e-02, + 9.014649142011735838e-01, 4.691007703066801815e-02, 2.273848306376403255e-02, 9.014649142011735838e-01, + 2.307653449471584461e-01, 2.273848306376403255e-02, 9.014649142011735838e-01, 5.000000000000000000e-01, + 2.273848306376403255e-02, 9.014649142011735838e-01, 7.692346550528414983e-01, 2.273848306376403255e-02, + 9.014649142011735838e-01, 9.530899229693319263e-01, 4.800950714742656666e-01, 3.980985705146872222e-02, + 4.691007703066801815e-02, 4.800950714742656666e-01, 3.980985705146872222e-02, 2.307653449471584461e-01, + 4.800950714742656666e-01, 3.980985705146872222e-02, 5.000000000000000000e-01, 4.800950714742656666e-01, + 3.980985705146872222e-02, 7.692346550528414983e-01, 4.800950714742656666e-01, 3.980985705146872222e-02, + 9.530899229693319263e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, 4.691007703066801815e-02, + 4.009932910631959224e-01, 1.980134178736082107e-01, 2.307653449471584461e-01, 4.009932910631959224e-01, + 1.980134178736082107e-01, 5.000000000000000000e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, + 7.692346550528414983e-01, 4.009932910631959224e-01, 1.980134178736082107e-01, 9.530899229693319263e-01, + 2.810125948763069204e-01, 4.379748102473861593e-01, 4.691007703066801815e-02, 2.810125948763069204e-01, + 4.379748102473861593e-01, 2.307653449471584461e-01, 2.810125948763069204e-01, 4.379748102473861593e-01, + 5.000000000000000000e-01, 2.810125948763069204e-01, 4.379748102473861593e-01, 7.692346550528414983e-01, + 2.810125948763069204e-01, 4.379748102473861593e-01, 9.530899229693319263e-01, 1.522678633231819567e-01, + 6.954642733536360311e-01, 4.691007703066801815e-02, 1.522678633231819567e-01, 6.954642733536360311e-01, + 2.307653449471584461e-01, 1.522678633231819567e-01, 6.954642733536360311e-01, 5.000000000000000000e-01, + 1.522678633231819567e-01, 6.954642733536360311e-01, 7.692346550528414983e-01, 1.522678633231819567e-01, + 6.954642733536360311e-01, 9.530899229693319263e-01, 4.926754289941320808e-02, 9.014649142011735838e-01, + 4.691007703066801815e-02, 4.926754289941320808e-02, 9.014649142011735838e-01, 2.307653449471584461e-01, + 4.926754289941320808e-02, 9.014649142011735838e-01, 5.000000000000000000e-01, 4.926754289941320808e-02, + 9.014649142011735838e-01, 7.692346550528414983e-01, 4.926754289941320808e-02, 9.014649142011735838e-01, + 9.530899229693319263e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, 4.691007703066801815e-02, + 7.386115333961520424e-01, 3.980985705146872222e-02, 2.307653449471584461e-01, 7.386115333961520424e-01, + 3.980985705146872222e-02, 5.000000000000000000e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, + 7.692346550528414983e-01, 7.386115333961520424e-01, 3.980985705146872222e-02, 9.530899229693319263e-01, + 6.169158718590023183e-01, 1.980134178736082107e-01, 4.691007703066801815e-02, 6.169158718590023183e-01, + 1.980134178736082107e-01, 2.307653449471584461e-01, 6.169158718590023183e-01, 1.980134178736082107e-01, + 5.000000000000000000e-01, 6.169158718590023183e-01, 1.980134178736082107e-01, 7.692346550528414983e-01, + 6.169158718590023183e-01, 1.980134178736082107e-01, 9.530899229693319263e-01, 4.323292529703596787e-01, + 4.379748102473861593e-01, 4.691007703066801815e-02, 4.323292529703596787e-01, 4.379748102473861593e-01, + 2.307653449471584461e-01, 4.323292529703596787e-01, 4.379748102473861593e-01, 5.000000000000000000e-01, + 4.323292529703596787e-01, 4.379748102473861593e-01, 7.692346550528414983e-01, 4.323292529703596787e-01, + 4.379748102473861593e-01, 9.530899229693319263e-01, 2.342594346380821868e-01, 6.954642733536360311e-01, + 4.691007703066801815e-02, 2.342594346380821868e-01, 6.954642733536360311e-01, 2.307653449471584461e-01, + 2.342594346380821868e-01, 6.954642733536360311e-01, 5.000000000000000000e-01, 2.342594346380821868e-01, + 6.954642733536360311e-01, 7.692346550528414983e-01, 2.342594346380821868e-01, 6.954642733536360311e-01, + 9.530899229693319263e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, 4.691007703066801815e-02, + 7.579660273506237667e-02, 9.014649142011735838e-01, 2.307653449471584461e-01, 7.579660273506237667e-02, + 9.014649142011735838e-01, 5.000000000000000000e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, + 7.692346550528414983e-01, 7.579660273506237667e-02, 9.014649142011735838e-01, 9.530899229693319263e-01, + 9.151475493787275539e-01, 3.980985705146872222e-02, 4.691007703066801815e-02, 9.151475493787275539e-01, + 3.980985705146872222e-02, 2.307653449471584461e-01, 9.151475493787275539e-01, 3.980985705146872222e-02, + 5.000000000000000000e-01, 9.151475493787275539e-01, 3.980985705146872222e-02, 7.692346550528414983e-01, + 9.151475493787275539e-01, 3.980985705146872222e-02, 9.530899229693319263e-01, 7.643653297812805780e-01, + 1.980134178736082107e-01, 4.691007703066801815e-02, 7.643653297812805780e-01, 1.980134178736082107e-01, + 2.307653449471584461e-01, 7.643653297812805780e-01, 1.980134178736082107e-01, 5.000000000000000000e-01, + 7.643653297812805780e-01, 1.980134178736082107e-01, 7.692346550528414983e-01, 7.643653297812805780e-01, + 1.980134178736082107e-01, 9.530899229693319263e-01, 5.356605448081428467e-01, 4.379748102473861593e-01, + 4.691007703066801815e-02, 5.356605448081428467e-01, 4.379748102473861593e-01, 2.307653449471584461e-01, + 5.356605448081428467e-01, 4.379748102473861593e-01, 5.000000000000000000e-01, 5.356605448081428467e-01, + 4.379748102473861593e-01, 7.692346550528414983e-01, 5.356605448081428467e-01, 4.379748102473861593e-01, + 9.530899229693319263e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, 4.691007703066801815e-02, + 2.902499322507924862e-01, 6.954642733536360311e-01, 2.307653449471584461e-01, 2.902499322507924862e-01, + 6.954642733536360311e-01, 5.000000000000000000e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, + 7.692346550528414983e-01, 2.902499322507924862e-01, 6.954642733536360311e-01, 9.530899229693319263e-01, + 9.391279733377998207e-02, 9.014649142011735838e-01, 4.691007703066801815e-02, 9.391279733377998207e-02, + 9.014649142011735838e-01, 2.307653449471584461e-01, 9.391279733377998207e-02, 9.014649142011735838e-01, + 5.000000000000000000e-01, 9.391279733377998207e-02, 9.014649142011735838e-01, 7.692346550528414983e-01, + 9.391279733377998207e-02, 9.014649142011735838e-01, 9.530899229693319263e-01 +}; +inline constexpr double prism_o9_weights[125] = { + 1.358192887310869131e-03, 2.743758082925574181e-03, 3.261178411119655240e-03, 2.743758082925574181e-03, + 1.358192887310869131e-03, 2.346059863934899480e-03, 4.739400989975380701e-03, 5.633161424226804104e-03, + 4.739400989975380701e-03, 2.346059863934899480e-03, 2.054334550482672574e-03, 4.150071083850390141e-03, + 4.932695162699575996e-03, 4.150071083850390141e-03, 2.054334550482672574e-03, 1.037206574171043762e-03, + 2.095316466558683026e-03, 2.490453100704378107e-03, 2.095316466558683026e-03, 1.037206574171043762e-03, + 2.209997319040949370e-04, 4.464533766899758996e-04, 5.306459496896958608e-04, 4.464533766899758996e-04, + 2.209997319040949370e-04, 2.743758082925574181e-03, 5.542812429628291536e-03, 6.588080904390647939e-03, + 5.542812429628291536e-03, 2.743758082925574181e-03, 4.739400989975380701e-03, 9.574317385962028554e-03, + 1.137985063428563655e-02, 9.574317385962028554e-03, 4.739400989975380701e-03, 4.150071083850391009e-03, + 8.383780527355944034e-03, 9.964801280959069071e-03, 8.383780527355944034e-03, 4.150071083850391009e-03, + 2.095316466558683026e-03, 4.232860844080959389e-03, 5.031097489204185841e-03, 4.232860844080959389e-03, + 2.095316466558683026e-03, 4.464533766899758996e-04, 9.019043409716836438e-04, 1.071986260004300241e-03, + 9.019043409716836438e-04, 4.464533766899758996e-04, 3.261178411119655240e-03, 6.588080904390647939e-03, + 7.830467033449181602e-03, 6.588080904390647939e-03, 3.261178411119655240e-03, 5.633161424226804104e-03, + 1.137985063428563655e-02, 1.352587294092913797e-02, 1.137985063428563655e-02, 5.633161424226804104e-03, + 4.932695162699575996e-03, 9.964801280959069071e-03, 1.184397232787767415e-02, 9.964801280959069071e-03, + 4.932695162699575996e-03, 2.490453100704377674e-03, 5.031097489204184973e-03, 5.979866307504941997e-03, + 5.031097489204184973e-03, 2.490453100704377674e-03, 5.306459496896957524e-04, 1.071986260004300241e-03, + 1.274142377893362679e-03, 1.071986260004300241e-03, 5.306459496896957524e-04, 2.743758082925574181e-03, + 5.542812429628291536e-03, 6.588080904390647939e-03, 5.542812429628291536e-03, 2.743758082925574181e-03, + 4.739400989975380701e-03, 9.574317385962028554e-03, 1.137985063428563655e-02, 9.574317385962028554e-03, + 4.739400989975380701e-03, 4.150071083850391009e-03, 8.383780527355944034e-03, 9.964801280959069071e-03, + 8.383780527355944034e-03, 4.150071083850391009e-03, 2.095316466558683026e-03, 4.232860844080959389e-03, + 5.031097489204185841e-03, 4.232860844080959389e-03, 2.095316466558683026e-03, 4.464533766899758996e-04, + 9.019043409716836438e-04, 1.071986260004300241e-03, 9.019043409716836438e-04, 4.464533766899758996e-04, + 1.358192887310869131e-03, 2.743758082925574181e-03, 3.261178411119655240e-03, 2.743758082925574181e-03, + 1.358192887310869131e-03, 2.346059863934899480e-03, 4.739400989975380701e-03, 5.633161424226804104e-03, + 4.739400989975380701e-03, 2.346059863934899480e-03, 2.054334550482672574e-03, 4.150071083850390141e-03, + 4.932695162699575996e-03, 4.150071083850390141e-03, 2.054334550482672574e-03, 1.037206574171043762e-03, + 2.095316466558683026e-03, 2.490453100704378107e-03, 2.095316466558683026e-03, 1.037206574171043762e-03, + 2.209997319040949370e-04, 4.464533766899758996e-04, 5.306459496896958608e-04, 4.464533766899758996e-04, + 2.209997319040949370e-04 +}; + +// ---- order 10: 216 point(s), tdim=3 ---- +inline constexpr int prism_o10_npts = 216; +inline constexpr int prism_o10_tdim = 3; +inline constexpr double prism_o10_points[648] = { + 3.277536661445988597e-02, 2.931642715978488578e-02, 3.376524289842397497e-02, 3.277536661445988597e-02, + 2.931642715978488578e-02, 1.693953067668677037e-01, 3.277536661445988597e-02, 2.931642715978488578e-02, + 3.806904069584015060e-01, 3.277536661445988597e-02, 2.931642715978488578e-02, 6.193095930415984940e-01, + 3.277536661445988597e-02, 2.931642715978488578e-02, 8.306046932331323518e-01, 3.277536661445988597e-02, + 2.931642715978488578e-02, 9.662347571015760250e-01, 2.876533301255911751e-02, 1.480785996684843009e-01, + 3.376524289842397497e-02, 2.876533301255911751e-02, 1.480785996684843009e-01, 1.693953067668677037e-01, + 2.876533301255911751e-02, 1.480785996684843009e-01, 3.806904069584015060e-01, 2.876533301255911751e-02, + 1.480785996684843009e-01, 6.193095930415984940e-01, 2.876533301255911751e-02, 1.480785996684843009e-01, + 8.306046932331323518e-01, 2.876533301255911751e-02, 1.480785996684843009e-01, 9.662347571015760250e-01, + 2.238687297803062734e-02, 3.369846902811542977e-01, 3.376524289842397497e-02, 2.238687297803062734e-02, + 3.369846902811542977e-01, 1.693953067668677037e-01, 2.238687297803062734e-02, 3.369846902811542977e-01, + 3.806904069584015060e-01, 2.238687297803062734e-02, 3.369846902811542977e-01, 6.193095930415984940e-01, + 2.238687297803062734e-02, 3.369846902811542977e-01, 8.306046932331323518e-01, 2.238687297803062734e-02, + 3.369846902811542977e-01, 9.662347571015760250e-01, 1.490156336667115486e-02, 5.586715187715501907e-01, + 3.376524289842397497e-02, 1.490156336667115486e-02, 5.586715187715501907e-01, 1.693953067668677037e-01, + 1.490156336667115486e-02, 5.586715187715501907e-01, 3.806904069584015060e-01, 1.490156336667115486e-02, + 5.586715187715501907e-01, 6.193095930415984940e-01, 1.490156336667115486e-02, 5.586715187715501907e-01, + 8.306046932331323518e-01, 1.490156336667115486e-02, 5.586715187715501907e-01, 9.662347571015760250e-01, + 7.791874701286428950e-03, 7.692338620300545049e-01, 3.376524289842397497e-02, 7.791874701286428950e-03, + 7.692338620300545049e-01, 1.693953067668677037e-01, 7.791874701286428950e-03, 7.692338620300545049e-01, + 3.806904069584015060e-01, 7.791874701286428950e-03, 7.692338620300545049e-01, 6.193095930415984940e-01, + 7.791874701286428950e-03, 7.692338620300545049e-01, 8.306046932331323518e-01, 7.791874701286428950e-03, + 7.692338620300545049e-01, 9.662347571015760250e-01, 2.466697152670243101e-03, 9.269456713197410380e-01, + 3.376524289842397497e-02, 2.466697152670243101e-03, 9.269456713197410380e-01, 1.693953067668677037e-01, + 2.466697152670243101e-03, 9.269456713197410380e-01, 3.806904069584015060e-01, 2.466697152670243101e-03, + 9.269456713197410380e-01, 6.193095930415984940e-01, 2.466697152670243101e-03, 9.269456713197410380e-01, + 8.306046932331323518e-01, 2.466697152670243101e-03, 9.269456713197410380e-01, 9.662347571015760250e-01, + 1.644292415948274133e-01, 2.931642715978488578e-02, 3.376524289842397497e-02, 1.644292415948274133e-01, + 2.931642715978488578e-02, 1.693953067668677037e-01, 1.644292415948274133e-01, 2.931642715978488578e-02, + 3.806904069584015060e-01, 1.644292415948274133e-01, 2.931642715978488578e-02, 6.193095930415984940e-01, + 1.644292415948274133e-01, 2.931642715978488578e-02, 8.306046932331323518e-01, 1.644292415948274133e-01, + 2.931642715978488578e-02, 9.662347571015760250e-01, 1.443114869504165954e-01, 1.480785996684843009e-01, + 3.376524289842397497e-02, 1.443114869504165954e-01, 1.480785996684843009e-01, 1.693953067668677037e-01, + 1.443114869504165954e-01, 1.480785996684843009e-01, 3.806904069584015060e-01, 1.443114869504165954e-01, + 1.480785996684843009e-01, 6.193095930415984940e-01, 1.443114869504165954e-01, 1.480785996684843009e-01, + 8.306046932331323518e-01, 1.443114869504165954e-01, 1.480785996684843009e-01, 9.662347571015760250e-01, + 1.123116817809536733e-01, 3.369846902811542977e-01, 3.376524289842397497e-02, 1.123116817809536733e-01, + 3.369846902811542977e-01, 1.693953067668677037e-01, 1.123116817809536733e-01, 3.369846902811542977e-01, + 3.806904069584015060e-01, 1.123116817809536733e-01, 3.369846902811542977e-01, 6.193095930415984940e-01, + 1.123116817809536733e-01, 3.369846902811542977e-01, 8.306046932331323518e-01, 1.123116817809536733e-01, + 3.369846902811542977e-01, 9.662347571015760250e-01, 7.475897346264907817e-02, 5.586715187715501907e-01, + 3.376524289842397497e-02, 7.475897346264907817e-02, 5.586715187715501907e-01, 1.693953067668677037e-01, + 7.475897346264907817e-02, 5.586715187715501907e-01, 3.806904069584015060e-01, 7.475897346264907817e-02, + 5.586715187715501907e-01, 6.193095930415984940e-01, 7.475897346264907817e-02, 5.586715187715501907e-01, + 8.306046932331323518e-01, 7.475897346264907817e-02, 5.586715187715501907e-01, 9.662347571015760250e-01, + 3.909070073282423091e-02, 7.692338620300545049e-01, 3.376524289842397497e-02, 3.909070073282423091e-02, + 7.692338620300545049e-01, 1.693953067668677037e-01, 3.909070073282423091e-02, 7.692338620300545049e-01, + 3.806904069584015060e-01, 3.909070073282423091e-02, 7.692338620300545049e-01, 6.193095930415984940e-01, + 3.909070073282423091e-02, 7.692338620300545049e-01, 8.306046932331323518e-01, 3.909070073282423091e-02, + 7.692338620300545049e-01, 9.662347571015760250e-01, 1.237506041744003936e-02, 9.269456713197410380e-01, + 3.376524289842397497e-02, 1.237506041744003936e-02, 9.269456713197410380e-01, 1.693953067668677037e-01, + 1.237506041744003936e-02, 9.269456713197410380e-01, 3.806904069584015060e-01, 1.237506041744003936e-02, + 9.269456713197410380e-01, 6.193095930415984940e-01, 1.237506041744003936e-02, 9.269456713197410380e-01, + 8.306046932331323518e-01, 1.237506041744003936e-02, 9.269456713197410380e-01, 9.662347571015760250e-01, + 3.695299243723766391e-01, 2.931642715978488578e-02, 3.376524289842397497e-02, 3.695299243723766391e-01, + 2.931642715978488578e-02, 1.693953067668677037e-01, 3.695299243723766391e-01, 2.931642715978488578e-02, + 3.806904069584015060e-01, 3.695299243723766391e-01, 2.931642715978488578e-02, 6.193095930415984940e-01, + 3.695299243723766391e-01, 2.931642715978488578e-02, 8.306046932331323518e-01, 3.695299243723766391e-01, + 2.931642715978488578e-02, 9.662347571015760250e-01, 3.243183045887759741e-01, 1.480785996684843009e-01, + 3.376524289842397497e-02, 3.243183045887759741e-01, 1.480785996684843009e-01, 1.693953067668677037e-01, + 3.243183045887759741e-01, 1.480785996684843009e-01, 3.806904069584015060e-01, 3.243183045887759741e-01, + 1.480785996684843009e-01, 6.193095930415984940e-01, 3.243183045887759741e-01, 1.480785996684843009e-01, + 8.306046932331323518e-01, 3.243183045887759741e-01, 1.480785996684843009e-01, 9.662347571015760250e-01, + 2.524035680765179812e-01, 3.369846902811542977e-01, 3.376524289842397497e-02, 2.524035680765179812e-01, + 3.369846902811542977e-01, 1.693953067668677037e-01, 2.524035680765179812e-01, 3.369846902811542977e-01, + 3.806904069584015060e-01, 2.524035680765179812e-01, 3.369846902811542977e-01, 6.193095930415984940e-01, + 2.524035680765179812e-01, 3.369846902811542977e-01, 8.306046932331323518e-01, 2.524035680765179812e-01, + 3.369846902811542977e-01, 9.662347571015760250e-01, 1.680095191211918304e-01, 5.586715187715501907e-01, + 3.376524289842397497e-02, 1.680095191211918304e-01, 5.586715187715501907e-01, 1.693953067668677037e-01, + 1.680095191211918304e-01, 5.586715187715501907e-01, 3.806904069584015060e-01, 1.680095191211918304e-01, + 5.586715187715501907e-01, 6.193095930415984940e-01, 1.680095191211918304e-01, 5.586715187715501907e-01, + 8.306046932331323518e-01, 1.680095191211918304e-01, 5.586715187715501907e-01, 9.662347571015760250e-01, + 8.785045497599718034e-02, 7.692338620300545049e-01, 3.376524289842397497e-02, 8.785045497599718034e-02, + 7.692338620300545049e-01, 1.693953067668677037e-01, 8.785045497599718034e-02, 7.692338620300545049e-01, + 3.806904069584015060e-01, 8.785045497599718034e-02, 7.692338620300545049e-01, 6.193095930415984940e-01, + 8.785045497599718034e-02, 7.692338620300545049e-01, 8.306046932331323518e-01, 8.785045497599718034e-02, + 7.692338620300545049e-01, 9.662347571015760250e-01, 2.781108211536058653e-02, 9.269456713197410380e-01, + 3.376524289842397497e-02, 2.781108211536058653e-02, 9.269456713197410380e-01, 1.693953067668677037e-01, + 2.781108211536058653e-02, 9.269456713197410380e-01, 3.806904069584015060e-01, 2.781108211536058653e-02, + 9.269456713197410380e-01, 6.193095930415984940e-01, 2.781108211536058653e-02, 9.269456713197410380e-01, + 8.306046932331323518e-01, 2.781108211536058653e-02, 9.269456713197410380e-01, 9.662347571015760250e-01, + 6.011536484678384751e-01, 2.931642715978488578e-02, 3.376524289842397497e-02, 6.011536484678384751e-01, + 2.931642715978488578e-02, 1.693953067668677037e-01, 6.011536484678384751e-01, 2.931642715978488578e-02, + 3.806904069584015060e-01, 6.011536484678384751e-01, 2.931642715978488578e-02, 6.193095930415984940e-01, + 6.011536484678384751e-01, 2.931642715978488578e-02, 8.306046932331323518e-01, 6.011536484678384751e-01, + 2.931642715978488578e-02, 9.662347571015760250e-01, 5.276030957427396695e-01, 1.480785996684843009e-01, + 3.376524289842397497e-02, 5.276030957427396695e-01, 1.480785996684843009e-01, 1.693953067668677037e-01, + 5.276030957427396695e-01, 1.480785996684843009e-01, 3.806904069584015060e-01, 5.276030957427396695e-01, + 1.480785996684843009e-01, 6.193095930415984940e-01, 5.276030957427396695e-01, 1.480785996684843009e-01, + 8.306046932331323518e-01, 5.276030957427396695e-01, 1.480785996684843009e-01, 9.662347571015760250e-01, + 4.106117416423277211e-01, 3.369846902811542977e-01, 3.376524289842397497e-02, 4.106117416423277211e-01, + 3.369846902811542977e-01, 1.693953067668677037e-01, 4.106117416423277211e-01, 3.369846902811542977e-01, + 3.806904069584015060e-01, 4.106117416423277211e-01, 3.369846902811542977e-01, 6.193095930415984940e-01, + 4.106117416423277211e-01, 3.369846902811542977e-01, 8.306046932331323518e-01, 4.106117416423277211e-01, + 3.369846902811542977e-01, 9.662347571015760250e-01, 2.733189621072580344e-01, 5.586715187715501907e-01, + 3.376524289842397497e-02, 2.733189621072580344e-01, 5.586715187715501907e-01, 1.693953067668677037e-01, + 2.733189621072580344e-01, 5.586715187715501907e-01, 3.806904069584015060e-01, 2.733189621072580344e-01, + 5.586715187715501907e-01, 6.193095930415984940e-01, 2.733189621072580344e-01, 5.586715187715501907e-01, + 8.306046932331323518e-01, 2.733189621072580344e-01, 5.586715187715501907e-01, 9.662347571015760250e-01, + 1.429156829939483009e-01, 7.692338620300545049e-01, 3.376524289842397497e-02, 1.429156829939483009e-01, + 7.692338620300545049e-01, 1.693953067668677037e-01, 1.429156829939483009e-01, 7.692338620300545049e-01, + 3.806904069584015060e-01, 1.429156829939483009e-01, 7.692338620300545049e-01, 6.193095930415984940e-01, + 1.429156829939483009e-01, 7.692338620300545049e-01, 8.306046932331323518e-01, 1.429156829939483009e-01, + 7.692338620300545049e-01, 9.662347571015760250e-01, 4.524324656489832341e-02, 9.269456713197410380e-01, + 3.376524289842397497e-02, 4.524324656489832341e-02, 9.269456713197410380e-01, 1.693953067668677037e-01, + 4.524324656489832341e-02, 9.269456713197410380e-01, 3.806904069584015060e-01, 4.524324656489832341e-02, + 9.269456713197410380e-01, 6.193095930415984940e-01, 4.524324656489832341e-02, 9.269456713197410380e-01, + 8.306046932331323518e-01, 4.524324656489832341e-02, 9.269456713197410380e-01, 9.662347571015760250e-01, + 8.062543312453878119e-01, 2.931642715978488578e-02, 3.376524289842397497e-02, 8.062543312453878119e-01, + 2.931642715978488578e-02, 1.693953067668677037e-01, 8.062543312453878119e-01, 2.931642715978488578e-02, + 3.806904069584015060e-01, 8.062543312453878119e-01, 2.931642715978488578e-02, 6.193095930415984940e-01, + 8.062543312453878119e-01, 2.931642715978488578e-02, 8.306046932331323518e-01, 8.062543312453878119e-01, + 2.931642715978488578e-02, 9.662347571015760250e-01, 7.076099133810991315e-01, 1.480785996684843009e-01, + 3.376524289842397497e-02, 7.076099133810991315e-01, 1.480785996684843009e-01, 1.693953067668677037e-01, + 7.076099133810991315e-01, 1.480785996684843009e-01, 3.806904069584015060e-01, 7.076099133810991315e-01, + 1.480785996684843009e-01, 6.193095930415984940e-01, 7.076099133810991315e-01, 1.480785996684843009e-01, + 8.306046932331323518e-01, 7.076099133810991315e-01, 1.480785996684843009e-01, 9.662347571015760250e-01, + 5.507036279378920707e-01, 3.369846902811542977e-01, 3.376524289842397497e-02, 5.507036279378920707e-01, + 3.369846902811542977e-01, 1.693953067668677037e-01, 5.507036279378920707e-01, 3.369846902811542977e-01, + 3.806904069584015060e-01, 5.507036279378920707e-01, 3.369846902811542977e-01, 6.193095930415984940e-01, + 5.507036279378920707e-01, 3.369846902811542977e-01, 8.306046932331323518e-01, 5.507036279378920707e-01, + 3.369846902811542977e-01, 9.662347571015760250e-01, 3.665695077658008283e-01, 5.586715187715501907e-01, + 3.376524289842397497e-02, 3.665695077658008283e-01, 5.586715187715501907e-01, 1.693953067668677037e-01, + 3.665695077658008283e-01, 5.586715187715501907e-01, 3.806904069584015060e-01, 3.665695077658008283e-01, + 5.586715187715501907e-01, 6.193095930415984940e-01, 3.665695077658008283e-01, 5.586715187715501907e-01, + 8.306046932331323518e-01, 3.665695077658008283e-01, 5.586715187715501907e-01, 9.662347571015760250e-01, + 1.916754372371212711e-01, 7.692338620300545049e-01, 3.376524289842397497e-02, 1.916754372371212711e-01, + 7.692338620300545049e-01, 1.693953067668677037e-01, 1.916754372371212711e-01, 7.692338620300545049e-01, + 3.806904069584015060e-01, 1.916754372371212711e-01, 7.692338620300545049e-01, 6.193095930415984940e-01, + 1.916754372371212711e-01, 7.692338620300545049e-01, 8.306046932331323518e-01, 1.916754372371212711e-01, + 7.692338620300545049e-01, 9.662347571015760250e-01, 6.067926826281887231e-02, 9.269456713197410380e-01, + 3.376524289842397497e-02, 6.067926826281887231e-02, 9.269456713197410380e-01, 1.693953067668677037e-01, + 6.067926826281887231e-02, 9.269456713197410380e-01, 3.806904069584015060e-01, 6.067926826281887231e-02, + 9.269456713197410380e-01, 6.193095930415984940e-01, 6.067926826281887231e-02, 9.269456713197410380e-01, + 8.306046932331323518e-01, 6.067926826281887231e-02, 9.269456713197410380e-01, 9.662347571015760250e-01, + 9.379082062257552144e-01, 2.931642715978488578e-02, 3.376524289842397497e-02, 9.379082062257552144e-01, + 2.931642715978488578e-02, 1.693953067668677037e-01, 9.379082062257552144e-01, 2.931642715978488578e-02, + 3.806904069584015060e-01, 9.379082062257552144e-01, 2.931642715978488578e-02, 6.193095930415984940e-01, + 9.379082062257552144e-01, 2.931642715978488578e-02, 8.306046932331323518e-01, 9.379082062257552144e-01, + 2.931642715978488578e-02, 9.662347571015760250e-01, 8.231560673189565192e-01, 1.480785996684843009e-01, + 3.376524289842397497e-02, 8.231560673189565192e-01, 1.480785996684843009e-01, 1.693953067668677037e-01, + 8.231560673189565192e-01, 1.480785996684843009e-01, 3.806904069584015060e-01, 8.231560673189565192e-01, + 1.480785996684843009e-01, 6.193095930415984940e-01, 8.231560673189565192e-01, 1.480785996684843009e-01, + 8.306046932331323518e-01, 8.231560673189565192e-01, 1.480785996684843009e-01, 9.662347571015760250e-01, + 6.406284367408150437e-01, 3.369846902811542977e-01, 3.376524289842397497e-02, 6.406284367408150437e-01, + 3.369846902811542977e-01, 1.693953067668677037e-01, 6.406284367408150437e-01, 3.369846902811542977e-01, + 3.806904069584015060e-01, 6.406284367408150437e-01, 3.369846902811542977e-01, 6.193095930415984940e-01, + 6.406284367408150437e-01, 3.369846902811542977e-01, 8.306046932331323518e-01, 6.406284367408150437e-01, + 3.369846902811542977e-01, 9.662347571015760250e-01, 4.264269178617787204e-01, 5.586715187715501907e-01, + 3.376524289842397497e-02, 4.264269178617787204e-01, 5.586715187715501907e-01, 1.693953067668677037e-01, + 4.264269178617787204e-01, 5.586715187715501907e-01, 3.806904069584015060e-01, 4.264269178617787204e-01, + 5.586715187715501907e-01, 6.193095930415984940e-01, 4.264269178617787204e-01, 5.586715187715501907e-01, + 8.306046932331323518e-01, 4.264269178617787204e-01, 5.586715187715501907e-01, 9.662347571015760250e-01, + 2.229742632686590731e-01, 7.692338620300545049e-01, 3.376524289842397497e-02, 2.229742632686590731e-01, + 7.692338620300545049e-01, 1.693953067668677037e-01, 2.229742632686590731e-01, 7.692338620300545049e-01, + 3.806904069584015060e-01, 2.229742632686590731e-01, 7.692338620300545049e-01, 6.193095930415984940e-01, + 2.229742632686590731e-01, 7.692338620300545049e-01, 8.306046932331323518e-01, 2.229742632686590731e-01, + 7.692338620300545049e-01, 9.662347571015760250e-01, 7.058763152758866510e-02, 9.269456713197410380e-01, + 3.376524289842397497e-02, 7.058763152758866510e-02, 9.269456713197410380e-01, 1.693953067668677037e-01, + 7.058763152758866510e-02, 9.269456713197410380e-01, 3.806904069584015060e-01, 7.058763152758866510e-02, + 9.269456713197410380e-01, 6.193095930415984940e-01, 7.058763152758866510e-02, 9.269456713197410380e-01, + 8.306046932331323518e-01, 7.058763152758866510e-02, 9.269456713197410380e-01, 9.662347571015760250e-01 +}; +inline constexpr double prism_o10_weights[216] = { + 5.306146836030765966e-04, 1.117326456251390050e-03, 1.449191536474944393e-03, 1.449191536474943526e-03, + 1.117326456251390050e-03, 5.306146836030765966e-04, 9.946136127669785287e-04, 2.094378722703475661e-03, + 2.716445048028285833e-03, 2.716445048028284098e-03, 2.094378722703475661e-03, 9.946136127669785287e-04, + 1.033138634997840619e-03, 2.175501669158562586e-03, 2.821662897976136113e-03, 2.821662897976134378e-03, + 2.175501669158562586e-03, 1.033138634997840619e-03, 7.239775401178304495e-04, 1.524494674388923764e-03, + 1.977295683964796699e-03, 1.977295683964795398e-03, 1.524494674388923764e-03, 7.239775401178304495e-04, + 3.225439024727977779e-04, 6.791874531029950526e-04, 8.809177507700397198e-04, 8.809177507700391777e-04, + 6.791874531029950526e-04, 3.225439024727977779e-04, 6.412183716400783975e-05, 1.350226959425480792e-04, + 1.751267475116022624e-04, 1.751267475116021540e-04, 1.350226959425480792e-04, 6.412183716400783975e-05, + 1.117326456251390050e-03, 2.352777728203922416e-03, 3.051592980586088997e-03, 3.051592980586086828e-03, + 2.352777728203922416e-03, 1.117326456251390050e-03, 2.094378722703475661e-03, 4.410177156041708568e-03, + 5.720075250283713705e-03, 5.720075250283710236e-03, 4.410177156041708568e-03, 2.094378722703475661e-03, + 2.175501669158562586e-03, 4.580999444011290937e-03, 5.941634681353970965e-03, 5.941634681353966628e-03, + 4.580999444011290937e-03, 2.175501669158562586e-03, 1.524494674388923981e-03, 3.210160375778973139e-03, + 4.163632948345241322e-03, 4.163632948345238720e-03, 3.210160375778973139e-03, 1.524494674388923981e-03, + 6.791874531029950526e-04, 1.430179249757905936e-03, 1.854966964037341919e-03, 1.854966964037340835e-03, + 1.430179249757905936e-03, 6.791874531029950526e-04, 1.350226959425480792e-04, 2.843201197271229253e-04, + 3.687680613421229540e-04, 3.687680613421227371e-04, 2.843201197271229253e-04, 1.350226959425480792e-04, + 1.449191536474944393e-03, 3.051592980586088563e-03, 3.957968322945281704e-03, 3.957968322945279102e-03, + 3.051592980586088563e-03, 1.449191536474944393e-03, 2.716445048028286267e-03, 5.720075250283714573e-03, + 7.419035497039986465e-03, 7.419035497039982129e-03, 5.720075250283714573e-03, 2.716445048028286267e-03, + 2.821662897976136113e-03, 5.941634681353970965e-03, 7.706401871063245715e-03, 7.706401871063241378e-03, + 5.941634681353970965e-03, 2.821662897976136113e-03, 1.977295683964796699e-03, 4.163632948345241322e-03, + 5.400303193369083293e-03, 5.400303193369079824e-03, 4.163632948345241322e-03, 1.977295683964796699e-03, + 8.809177507700397198e-04, 1.854966964037341919e-03, 2.405923899575786173e-03, 2.405923899575784872e-03, + 1.854966964037341919e-03, 8.809177507700397198e-04, 1.751267475116022353e-04, 3.687680613421228997e-04, + 4.782984869187042434e-04, 4.782984869187039724e-04, 3.687680613421228997e-04, 1.751267475116022353e-04, + 1.449191536474943526e-03, 3.051592980586086828e-03, 3.957968322945279102e-03, 3.957968322945277367e-03, + 3.051592980586086828e-03, 1.449191536474943526e-03, 2.716445048028284532e-03, 5.720075250283710236e-03, + 7.419035497039981261e-03, 7.419035497039976924e-03, 5.720075250283710236e-03, 2.716445048028284532e-03, + 2.821662897976134378e-03, 5.941634681353966628e-03, 7.706401871063240511e-03, 7.706401871063236174e-03, + 5.941634681353966628e-03, 2.821662897976134378e-03, 1.977295683964795832e-03, 4.163632948345238720e-03, + 5.400303193369079824e-03, 5.400303193369077222e-03, 4.163632948345238720e-03, 1.977295683964795832e-03, + 8.809177507700392861e-04, 1.854966964037341052e-03, 2.405923899575785306e-03, 2.405923899575783571e-03, + 1.854966964037341052e-03, 8.809177507700392861e-04, 1.751267475116021811e-04, 3.687680613421227371e-04, + 4.782984869187040266e-04, 4.782984869187037556e-04, 3.687680613421227371e-04, 1.751267475116021811e-04, + 1.117326456251390050e-03, 2.352777728203922416e-03, 3.051592980586088997e-03, 3.051592980586086828e-03, + 2.352777728203922416e-03, 1.117326456251390050e-03, 2.094378722703475661e-03, 4.410177156041708568e-03, + 5.720075250283713705e-03, 5.720075250283710236e-03, 4.410177156041708568e-03, 2.094378722703475661e-03, + 2.175501669158562586e-03, 4.580999444011290937e-03, 5.941634681353970965e-03, 5.941634681353966628e-03, + 4.580999444011290937e-03, 2.175501669158562586e-03, 1.524494674388923981e-03, 3.210160375778973139e-03, + 4.163632948345241322e-03, 4.163632948345238720e-03, 3.210160375778973139e-03, 1.524494674388923981e-03, + 6.791874531029950526e-04, 1.430179249757905936e-03, 1.854966964037341919e-03, 1.854966964037340835e-03, + 1.430179249757905936e-03, 6.791874531029950526e-04, 1.350226959425480792e-04, 2.843201197271229253e-04, + 3.687680613421229540e-04, 3.687680613421227371e-04, 2.843201197271229253e-04, 1.350226959425480792e-04, + 5.306146836030765966e-04, 1.117326456251390050e-03, 1.449191536474944393e-03, 1.449191536474943526e-03, + 1.117326456251390050e-03, 5.306146836030765966e-04, 9.946136127669785287e-04, 2.094378722703475661e-03, + 2.716445048028285833e-03, 2.716445048028284098e-03, 2.094378722703475661e-03, 9.946136127669785287e-04, + 1.033138634997840619e-03, 2.175501669158562586e-03, 2.821662897976136113e-03, 2.821662897976134378e-03, + 2.175501669158562586e-03, 1.033138634997840619e-03, 7.239775401178304495e-04, 1.524494674388923764e-03, + 1.977295683964796699e-03, 1.977295683964795398e-03, 1.524494674388923764e-03, 7.239775401178304495e-04, + 3.225439024727977779e-04, 6.791874531029950526e-04, 8.809177507700397198e-04, 8.809177507700391777e-04, + 6.791874531029950526e-04, 3.225439024727977779e-04, 6.412183716400783975e-05, 1.350226959425480792e-04, + 1.751267475116022624e-04, 1.751267475116021540e-04, 1.350226959425480792e-04, 6.412183716400783975e-05 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_pyramid.h b/cpp/src/generated/quadrature_tables_pyramid.h new file mode 100644 index 0000000..6eae09f --- /dev/null +++ b/cpp/src/generated/quadrature_tables_pyramid.h @@ -0,0 +1,1312 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'pyramid', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 8 point(s), tdim=3 ---- +inline constexpr int pyramid_o1_npts = 8; +inline constexpr int pyramid_o1_tdim = 3; +inline constexpr double pyramid_o1_points[24] = { + 1.666666666666666852e-01, 1.666666666666666852e-01, 2.113248654051871345e-01, 4.465819873852045624e-02, + 4.465819873852045624e-02, 7.886751345948128655e-01, 1.666666666666666852e-01, 6.220084679281462359e-01, + 2.113248654051871345e-01, 4.465819873852045624e-02, 1.666666666666666852e-01, 7.886751345948128655e-01, + 6.220084679281462359e-01, 1.666666666666666852e-01, 2.113248654051871345e-01, 1.666666666666666852e-01, + 4.465819873852045624e-02, 7.886751345948128655e-01, 6.220084679281462359e-01, 6.220084679281462359e-01, + 2.113248654051871345e-01, 1.666666666666666852e-01, 1.666666666666666852e-01, 7.886751345948128655e-01 +}; +inline constexpr double pyramid_o1_weights[8] = { + 7.775105849101827948e-02, 5.582274842315057030e-03, 7.775105849101827948e-02, 5.582274842315057030e-03, + 7.775105849101827948e-02, 5.582274842315057030e-03, 7.775105849101827948e-02, 5.582274842315057030e-03 +}; + +// ---- order 2: 27 point(s), tdim=3 ---- +inline constexpr int pyramid_o2_npts = 27; +inline constexpr int pyramid_o2_tdim = 3; +inline constexpr double pyramid_o2_points[81] = { + 9.999999999999999167e-02, 9.999999999999999167e-02, 1.127016653792582979e-01, 5.635083268962914893e-02, + 5.635083268962914893e-02, 5.000000000000000000e-01, 1.270166537925830792e-02, 1.270166537925830792e-02, + 8.872983346207417021e-01, 9.999999999999999167e-02, 4.436491673103708511e-01, 1.127016653792582979e-01, + 5.635083268962914893e-02, 2.500000000000000000e-01, 5.000000000000000000e-01, 1.270166537925830792e-02, + 5.635083268962914893e-02, 8.872983346207417021e-01, 9.999999999999999167e-02, 7.872983346207417243e-01, + 1.127016653792582979e-01, 5.635083268962914893e-02, 4.436491673103708511e-01, 5.000000000000000000e-01, + 1.270166537925830792e-02, 9.999999999999999167e-02, 8.872983346207417021e-01, 4.436491673103708511e-01, + 9.999999999999999167e-02, 1.127016653792582979e-01, 2.500000000000000000e-01, 5.635083268962914893e-02, + 5.000000000000000000e-01, 5.635083268962914893e-02, 1.270166537925830792e-02, 8.872983346207417021e-01, + 4.436491673103708511e-01, 4.436491673103708511e-01, 1.127016653792582979e-01, 2.500000000000000000e-01, + 2.500000000000000000e-01, 5.000000000000000000e-01, 5.635083268962914893e-02, 5.635083268962914893e-02, + 8.872983346207417021e-01, 4.436491673103708511e-01, 7.872983346207417243e-01, 1.127016653792582979e-01, + 2.500000000000000000e-01, 4.436491673103708511e-01, 5.000000000000000000e-01, 5.635083268962914893e-02, + 9.999999999999999167e-02, 8.872983346207417021e-01, 7.872983346207417243e-01, 9.999999999999999167e-02, + 1.127016653792582979e-01, 4.436491673103708511e-01, 5.635083268962914893e-02, 5.000000000000000000e-01, + 9.999999999999999167e-02, 1.270166537925830792e-02, 8.872983346207417021e-01, 7.872983346207417243e-01, + 4.436491673103708511e-01, 1.127016653792582979e-01, 4.436491673103708511e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 9.999999999999999167e-02, 5.635083268962914893e-02, 8.872983346207417021e-01, + 7.872983346207417243e-01, 7.872983346207417243e-01, 1.127016653792582979e-01, 4.436491673103708511e-01, + 4.436491673103708511e-01, 5.000000000000000000e-01, 9.999999999999999167e-02, 9.999999999999999167e-02, + 8.872983346207417021e-01 +}; +inline constexpr double pyramid_o2_weights[27] = { + 1.687453563573261539e-02, 8.573388203017826004e-03, 2.722407703030327135e-04, 2.699925701717219295e-02, + 1.371742112482852646e-02, 4.355852324848524934e-04, 1.687453563573261539e-02, 8.573388203017826004e-03, + 2.722407703030327135e-04, 2.699925701717219295e-02, 1.371742112482852646e-02, 4.355852324848524934e-04, + 4.319881122747552121e-02, 2.194787379972564997e-02, 6.969363719757642062e-04, 2.699925701717219295e-02, + 1.371742112482852646e-02, 4.355852324848524934e-04, 1.687453563573261539e-02, 8.573388203017826004e-03, + 2.722407703030327135e-04, 2.699925701717219295e-02, 1.371742112482852646e-02, 4.355852324848524934e-04, + 1.687453563573261539e-02, 8.573388203017826004e-03, 2.722407703030327135e-04 +}; + +// ---- order 3: 27 point(s), tdim=3 ---- +inline constexpr int pyramid_o3_npts = 27; +inline constexpr int pyramid_o3_tdim = 3; +inline constexpr double pyramid_o3_points[81] = { + 9.999999999999999167e-02, 9.999999999999999167e-02, 1.127016653792582979e-01, 5.635083268962914893e-02, + 5.635083268962914893e-02, 5.000000000000000000e-01, 1.270166537925830792e-02, 1.270166537925830792e-02, + 8.872983346207417021e-01, 9.999999999999999167e-02, 4.436491673103708511e-01, 1.127016653792582979e-01, + 5.635083268962914893e-02, 2.500000000000000000e-01, 5.000000000000000000e-01, 1.270166537925830792e-02, + 5.635083268962914893e-02, 8.872983346207417021e-01, 9.999999999999999167e-02, 7.872983346207417243e-01, + 1.127016653792582979e-01, 5.635083268962914893e-02, 4.436491673103708511e-01, 5.000000000000000000e-01, + 1.270166537925830792e-02, 9.999999999999999167e-02, 8.872983346207417021e-01, 4.436491673103708511e-01, + 9.999999999999999167e-02, 1.127016653792582979e-01, 2.500000000000000000e-01, 5.635083268962914893e-02, + 5.000000000000000000e-01, 5.635083268962914893e-02, 1.270166537925830792e-02, 8.872983346207417021e-01, + 4.436491673103708511e-01, 4.436491673103708511e-01, 1.127016653792582979e-01, 2.500000000000000000e-01, + 2.500000000000000000e-01, 5.000000000000000000e-01, 5.635083268962914893e-02, 5.635083268962914893e-02, + 8.872983346207417021e-01, 4.436491673103708511e-01, 7.872983346207417243e-01, 1.127016653792582979e-01, + 2.500000000000000000e-01, 4.436491673103708511e-01, 5.000000000000000000e-01, 5.635083268962914893e-02, + 9.999999999999999167e-02, 8.872983346207417021e-01, 7.872983346207417243e-01, 9.999999999999999167e-02, + 1.127016653792582979e-01, 4.436491673103708511e-01, 5.635083268962914893e-02, 5.000000000000000000e-01, + 9.999999999999999167e-02, 1.270166537925830792e-02, 8.872983346207417021e-01, 7.872983346207417243e-01, + 4.436491673103708511e-01, 1.127016653792582979e-01, 4.436491673103708511e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 9.999999999999999167e-02, 5.635083268962914893e-02, 8.872983346207417021e-01, + 7.872983346207417243e-01, 7.872983346207417243e-01, 1.127016653792582979e-01, 4.436491673103708511e-01, + 4.436491673103708511e-01, 5.000000000000000000e-01, 9.999999999999999167e-02, 9.999999999999999167e-02, + 8.872983346207417021e-01 +}; +inline constexpr double pyramid_o3_weights[27] = { + 1.687453563573261539e-02, 8.573388203017826004e-03, 2.722407703030327135e-04, 2.699925701717219295e-02, + 1.371742112482852646e-02, 4.355852324848524934e-04, 1.687453563573261539e-02, 8.573388203017826004e-03, + 2.722407703030327135e-04, 2.699925701717219295e-02, 1.371742112482852646e-02, 4.355852324848524934e-04, + 4.319881122747552121e-02, 2.194787379972564997e-02, 6.969363719757642062e-04, 2.699925701717219295e-02, + 1.371742112482852646e-02, 4.355852324848524934e-04, 1.687453563573261539e-02, 8.573388203017826004e-03, + 2.722407703030327135e-04, 2.699925701717219295e-02, 1.371742112482852646e-02, 4.355852324848524934e-04, + 1.687453563573261539e-02, 8.573388203017826004e-03, 2.722407703030327135e-04 +}; + +// ---- order 4: 64 point(s), tdim=3 ---- +inline constexpr int pyramid_o4_npts = 64; +inline constexpr int pyramid_o4_tdim = 3; +inline constexpr double pyramid_o4_points[192] = { + 6.461106321354768978e-02, 6.461106321354768978e-02, 6.943184420297371373e-02, 4.651867752656093508e-02, + 4.651867752656093508e-02, 3.300094782075718713e-01, 2.291316667641277866e-02, 2.291316667641277866e-02, + 6.699905217924281287e-01, 4.820780989426018751e-03, 4.820780989426018751e-03, 9.305681557970262308e-01, + 6.461106321354768978e-02, 3.070963115311590719e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 2.211032225007380225e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 1.089062557068338488e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 2.291316667641279600e-02, 9.305681557970262308e-01, + 6.461106321354768978e-02, 6.234718442658671034e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 4.488872992916901339e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 2.211032225007380225e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 4.651867752656096977e-02, 9.305681557970262308e-01, + 6.461106321354768978e-02, 8.659570925834785271e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 6.234718442658671034e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 3.070963115311590719e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 6.461106321354774529e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 2.211032225007380225e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 1.089062557068338488e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 2.291316667641279600e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 3.070963115311590719e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 6.461106321354774529e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 4.488872992916901339e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 2.211032225007380225e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 4.651867752656096977e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 6.234718442658671034e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 6.461106321354774529e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 6.234718442658671034e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 3.070963115311590719e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 6.461106321354774529e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 8.659570925834785271e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 6.461106321354774529e-02, 9.305681557970262308e-01 +}; +inline constexpr double pyramid_o4_weights[64] = { + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 1.601374791094413094e-02, 1.556252803580992529e-02, 3.775684142516485663e-03, 8.914849495397851294e-05, + 1.601374791094412400e-02, 1.556252803580992182e-02, 3.775684142516484362e-03, 8.914849495397848583e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 1.601374791094412400e-02, 1.556252803580992182e-02, 3.775684142516484362e-03, 8.914849495397848583e-05, + 1.601374791094412053e-02, 1.556252803580991662e-02, 3.775684142516482628e-03, 8.914849495397845873e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05 +}; + +// ---- order 5: 64 point(s), tdim=3 ---- +inline constexpr int pyramid_o5_npts = 64; +inline constexpr int pyramid_o5_tdim = 3; +inline constexpr double pyramid_o5_points[192] = { + 6.461106321354768978e-02, 6.461106321354768978e-02, 6.943184420297371373e-02, 4.651867752656093508e-02, + 4.651867752656093508e-02, 3.300094782075718713e-01, 2.291316667641277866e-02, 2.291316667641277866e-02, + 6.699905217924281287e-01, 4.820780989426018751e-03, 4.820780989426018751e-03, 9.305681557970262308e-01, + 6.461106321354768978e-02, 3.070963115311590719e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 2.211032225007380225e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 1.089062557068338488e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 2.291316667641279600e-02, 9.305681557970262308e-01, + 6.461106321354768978e-02, 6.234718442658671034e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 4.488872992916901339e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 2.211032225007380225e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 4.651867752656096977e-02, 9.305681557970262308e-01, + 6.461106321354768978e-02, 8.659570925834785271e-01, 6.943184420297371373e-02, 4.651867752656093508e-02, + 6.234718442658671034e-01, 3.300094782075718713e-01, 2.291316667641277866e-02, 3.070963115311590719e-01, + 6.699905217924281287e-01, 4.820780989426018751e-03, 6.461106321354774529e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 2.211032225007380225e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 1.089062557068338488e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 2.291316667641279600e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 3.070963115311590719e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 3.070963115311590719e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 2.211032225007380225e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 1.089062557068338488e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 2.291316667641279600e-02, 6.461106321354774529e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 4.488872992916901339e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 2.211032225007380225e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 4.651867752656096977e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 6.234718442658671034e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 6.234718442658671034e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 4.488872992916901339e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 2.211032225007380225e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 4.651867752656096977e-02, 6.461106321354774529e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 6.461106321354768978e-02, 6.943184420297371373e-02, 6.234718442658671034e-01, + 4.651867752656093508e-02, 3.300094782075718713e-01, 3.070963115311590719e-01, 2.291316667641277866e-02, + 6.699905217924281287e-01, 6.461106321354774529e-02, 4.820780989426018751e-03, 9.305681557970262308e-01, + 8.659570925834785271e-01, 3.070963115311590719e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 2.211032225007380225e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 1.089062557068338488e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 2.291316667641279600e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 6.234718442658671034e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 4.488872992916901339e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 2.211032225007380225e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 4.651867752656096977e-02, 9.305681557970262308e-01, + 8.659570925834785271e-01, 8.659570925834785271e-01, 6.943184420297371373e-02, 6.234718442658671034e-01, + 6.234718442658671034e-01, 3.300094782075718713e-01, 3.070963115311590719e-01, 3.070963115311590719e-01, + 6.699905217924281287e-01, 6.461106321354774529e-02, 6.461106321354774529e-02, 9.305681557970262308e-01 +}; +inline constexpr double pyramid_o5_weights[64] = { + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 1.601374791094413094e-02, 1.556252803580992529e-02, 3.775684142516485663e-03, 8.914849495397851294e-05, + 1.601374791094412400e-02, 1.556252803580992182e-02, 3.775684142516484362e-03, 8.914849495397848583e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 1.601374791094412400e-02, 1.556252803580992182e-02, 3.775684142516484362e-03, 8.914849495397848583e-05, + 1.601374791094412053e-02, 1.556252803580991662e-02, 3.775684142516482628e-03, 8.914849495397845873e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05, + 8.541748348656808335e-03, 8.301067238604197679e-03, 2.013953508494343170e-03, 4.755189189895837415e-05, + 8.541748348656806600e-03, 8.301067238604195944e-03, 2.013953508494342737e-03, 4.755189189895836738e-05, + 4.556176683780436241e-03, 4.427797151032841425e-03, 1.074244714673975188e-03, 2.536422431290088166e-05 +}; + +// ---- order 6: 125 point(s), tdim=3 ---- +inline constexpr int pyramid_o6_npts = 125; +inline constexpr int pyramid_o6_tdim = 3; +inline constexpr double pyramid_o6_points[375] = { + 4.470952170364481115e-02, 4.470952170364481115e-02, 4.691007703066801815e-02, 3.608485692318813554e-02, + 3.608485692318813554e-02, 2.307653449471584461e-01, 2.345503851533400907e-02, 2.345503851533400907e-02, + 5.000000000000000000e-01, 1.082522010747988261e-02, 1.082522010747988261e-02, 7.692346550528414983e-01, + 2.200555327023209599e-03, 2.200555327023209599e-03, 9.530899229693319263e-01, 4.470952170364481115e-02, + 2.199401248396785635e-01, 4.691007703066801815e-02, 3.608485692318813554e-02, 1.775127005185773987e-01, + 2.307653449471584461e-01, 2.345503851533400907e-02, 1.153826724735792231e-01, 5.000000000000000000e-01, + 1.082522010747988261e-02, 5.325264442858104047e-02, 7.692346550528414983e-01, 2.200555327023209599e-03, + 1.082522010747989302e-02, 9.530899229693319263e-01, 4.470952170364481115e-02, 4.765449614846659632e-01, + 4.691007703066801815e-02, 3.608485692318813554e-02, 3.846173275264207492e-01, 2.307653449471584461e-01, + 2.345503851533400907e-02, 2.500000000000000000e-01, 5.000000000000000000e-01, 1.082522010747988261e-02, + 1.153826724735792508e-01, 7.692346550528414983e-01, 2.200555327023209599e-03, 2.345503851533403683e-02, + 9.530899229693319263e-01, 4.470952170364481115e-02, 7.331497981296533073e-01, 4.691007703066801815e-02, + 3.608485692318813554e-02, 5.917219545342640163e-01, 2.307653449471584461e-01, 2.345503851533400907e-02, + 3.846173275264207492e-01, 5.000000000000000000e-01, 1.082522010747988261e-02, 1.775127005185774542e-01, + 7.692346550528414983e-01, 2.200555327023209599e-03, 3.608485692318817717e-02, 9.530899229693319263e-01, + 4.470952170364481115e-02, 9.083804012656870874e-01, 4.691007703066801815e-02, 3.608485692318813554e-02, + 7.331497981296533073e-01, 2.307653449471584461e-01, 2.345503851533400907e-02, 4.765449614846659632e-01, + 5.000000000000000000e-01, 1.082522010747988261e-02, 2.199401248396786190e-01, 7.692346550528414983e-01, + 2.200555327023209599e-03, 4.470952170364485972e-02, 9.530899229693319263e-01, 2.199401248396785635e-01, + 4.470952170364481115e-02, 4.691007703066801815e-02, 1.775127005185773987e-01, 3.608485692318813554e-02, + 2.307653449471584461e-01, 1.153826724735792231e-01, 2.345503851533400907e-02, 5.000000000000000000e-01, + 5.325264442858104047e-02, 1.082522010747988261e-02, 7.692346550528414983e-01, 1.082522010747989302e-02, + 2.200555327023209599e-03, 9.530899229693319263e-01, 2.199401248396785635e-01, 2.199401248396785635e-01, + 4.691007703066801815e-02, 1.775127005185773987e-01, 1.775127005185773987e-01, 2.307653449471584461e-01, + 1.153826724735792231e-01, 1.153826724735792231e-01, 5.000000000000000000e-01, 5.325264442858104047e-02, + 5.325264442858104047e-02, 7.692346550528414983e-01, 1.082522010747989302e-02, 1.082522010747989302e-02, + 9.530899229693319263e-01, 2.199401248396785635e-01, 4.765449614846659632e-01, 4.691007703066801815e-02, + 1.775127005185773987e-01, 3.846173275264207492e-01, 2.307653449471584461e-01, 1.153826724735792231e-01, + 2.500000000000000000e-01, 5.000000000000000000e-01, 5.325264442858104047e-02, 1.153826724735792508e-01, + 7.692346550528414983e-01, 1.082522010747989302e-02, 2.345503851533403683e-02, 9.530899229693319263e-01, + 2.199401248396785635e-01, 7.331497981296533073e-01, 4.691007703066801815e-02, 1.775127005185773987e-01, + 5.917219545342640163e-01, 2.307653449471584461e-01, 1.153826724735792231e-01, 3.846173275264207492e-01, + 5.000000000000000000e-01, 5.325264442858104047e-02, 1.775127005185774542e-01, 7.692346550528414983e-01, + 1.082522010747989302e-02, 3.608485692318817717e-02, 9.530899229693319263e-01, 2.199401248396785635e-01, + 9.083804012656870874e-01, 4.691007703066801815e-02, 1.775127005185773987e-01, 7.331497981296533073e-01, + 2.307653449471584461e-01, 1.153826724735792231e-01, 4.765449614846659632e-01, 5.000000000000000000e-01, + 5.325264442858104047e-02, 2.199401248396786190e-01, 7.692346550528414983e-01, 1.082522010747989302e-02, + 4.470952170364485972e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, 4.470952170364481115e-02, + 4.691007703066801815e-02, 3.846173275264207492e-01, 3.608485692318813554e-02, 2.307653449471584461e-01, + 2.500000000000000000e-01, 2.345503851533400907e-02, 5.000000000000000000e-01, 1.153826724735792508e-01, + 1.082522010747988261e-02, 7.692346550528414983e-01, 2.345503851533403683e-02, 2.200555327023209599e-03, + 9.530899229693319263e-01, 4.765449614846659632e-01, 2.199401248396785635e-01, 4.691007703066801815e-02, + 3.846173275264207492e-01, 1.775127005185773987e-01, 2.307653449471584461e-01, 2.500000000000000000e-01, + 1.153826724735792231e-01, 5.000000000000000000e-01, 1.153826724735792508e-01, 5.325264442858104047e-02, + 7.692346550528414983e-01, 2.345503851533403683e-02, 1.082522010747989302e-02, 9.530899229693319263e-01, + 4.765449614846659632e-01, 4.765449614846659632e-01, 4.691007703066801815e-02, 3.846173275264207492e-01, + 3.846173275264207492e-01, 2.307653449471584461e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 1.153826724735792508e-01, 1.153826724735792508e-01, 7.692346550528414983e-01, + 2.345503851533403683e-02, 2.345503851533403683e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, + 7.331497981296533073e-01, 4.691007703066801815e-02, 3.846173275264207492e-01, 5.917219545342640163e-01, + 2.307653449471584461e-01, 2.500000000000000000e-01, 3.846173275264207492e-01, 5.000000000000000000e-01, + 1.153826724735792508e-01, 1.775127005185774542e-01, 7.692346550528414983e-01, 2.345503851533403683e-02, + 3.608485692318817717e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, 9.083804012656870874e-01, + 4.691007703066801815e-02, 3.846173275264207492e-01, 7.331497981296533073e-01, 2.307653449471584461e-01, + 2.500000000000000000e-01, 4.765449614846659632e-01, 5.000000000000000000e-01, 1.153826724735792508e-01, + 2.199401248396786190e-01, 7.692346550528414983e-01, 2.345503851533403683e-02, 4.470952170364485972e-02, + 9.530899229693319263e-01, 7.331497981296533073e-01, 4.470952170364481115e-02, 4.691007703066801815e-02, + 5.917219545342640163e-01, 3.608485692318813554e-02, 2.307653449471584461e-01, 3.846173275264207492e-01, + 2.345503851533400907e-02, 5.000000000000000000e-01, 1.775127005185774542e-01, 1.082522010747988261e-02, + 7.692346550528414983e-01, 3.608485692318817717e-02, 2.200555327023209599e-03, 9.530899229693319263e-01, + 7.331497981296533073e-01, 2.199401248396785635e-01, 4.691007703066801815e-02, 5.917219545342640163e-01, + 1.775127005185773987e-01, 2.307653449471584461e-01, 3.846173275264207492e-01, 1.153826724735792231e-01, + 5.000000000000000000e-01, 1.775127005185774542e-01, 5.325264442858104047e-02, 7.692346550528414983e-01, + 3.608485692318817717e-02, 1.082522010747989302e-02, 9.530899229693319263e-01, 7.331497981296533073e-01, + 4.765449614846659632e-01, 4.691007703066801815e-02, 5.917219545342640163e-01, 3.846173275264207492e-01, + 2.307653449471584461e-01, 3.846173275264207492e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, + 1.775127005185774542e-01, 1.153826724735792508e-01, 7.692346550528414983e-01, 3.608485692318817717e-02, + 2.345503851533403683e-02, 9.530899229693319263e-01, 7.331497981296533073e-01, 7.331497981296533073e-01, + 4.691007703066801815e-02, 5.917219545342640163e-01, 5.917219545342640163e-01, 2.307653449471584461e-01, + 3.846173275264207492e-01, 3.846173275264207492e-01, 5.000000000000000000e-01, 1.775127005185774542e-01, + 1.775127005185774542e-01, 7.692346550528414983e-01, 3.608485692318817717e-02, 3.608485692318817717e-02, + 9.530899229693319263e-01, 7.331497981296533073e-01, 9.083804012656870874e-01, 4.691007703066801815e-02, + 5.917219545342640163e-01, 7.331497981296533073e-01, 2.307653449471584461e-01, 3.846173275264207492e-01, + 4.765449614846659632e-01, 5.000000000000000000e-01, 1.775127005185774542e-01, 2.199401248396786190e-01, + 7.692346550528414983e-01, 3.608485692318817717e-02, 4.470952170364485972e-02, 9.530899229693319263e-01, + 9.083804012656870874e-01, 4.470952170364481115e-02, 4.691007703066801815e-02, 7.331497981296533073e-01, + 3.608485692318813554e-02, 2.307653449471584461e-01, 4.765449614846659632e-01, 2.345503851533400907e-02, + 5.000000000000000000e-01, 2.199401248396786190e-01, 1.082522010747988261e-02, 7.692346550528414983e-01, + 4.470952170364485972e-02, 2.200555327023209599e-03, 9.530899229693319263e-01, 9.083804012656870874e-01, + 2.199401248396785635e-01, 4.691007703066801815e-02, 7.331497981296533073e-01, 1.775127005185773987e-01, + 2.307653449471584461e-01, 4.765449614846659632e-01, 1.153826724735792231e-01, 5.000000000000000000e-01, + 2.199401248396786190e-01, 5.325264442858104047e-02, 7.692346550528414983e-01, 4.470952170364485972e-02, + 1.082522010747989302e-02, 9.530899229693319263e-01, 9.083804012656870874e-01, 4.765449614846659632e-01, + 4.691007703066801815e-02, 7.331497981296533073e-01, 3.846173275264207492e-01, 2.307653449471584461e-01, + 4.765449614846659632e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, 2.199401248396786190e-01, + 1.153826724735792508e-01, 7.692346550528414983e-01, 4.470952170364485972e-02, 2.345503851533403683e-02, + 9.530899229693319263e-01, 9.083804012656870874e-01, 7.331497981296533073e-01, 4.691007703066801815e-02, + 7.331497981296533073e-01, 5.917219545342640163e-01, 2.307653449471584461e-01, 4.765449614846659632e-01, + 3.846173275264207492e-01, 5.000000000000000000e-01, 2.199401248396786190e-01, 1.775127005185774542e-01, + 7.692346550528414983e-01, 4.470952170364485972e-02, 3.608485692318817717e-02, 9.530899229693319263e-01, + 9.083804012656870874e-01, 9.083804012656870874e-01, 4.691007703066801815e-02, 7.331497981296533073e-01, + 7.331497981296533073e-01, 2.307653449471584461e-01, 4.765449614846659632e-01, 4.765449614846659632e-01, + 5.000000000000000000e-01, 2.199401248396786190e-01, 2.199401248396786190e-01, 7.692346550528414983e-01, + 4.470952170364485972e-02, 4.470952170364485972e-02, 9.530899229693319263e-01 +}; +inline constexpr double pyramid_o6_weights[125] = { + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 6.162962610875639263e-03, 8.110055490713787008e-03, + 4.072629408426510847e-03, 7.298730392435259871e-04, 1.492980273981122173e-05, 7.325179555806502624e-03, + 9.639456934591433129e-03, 4.840649467568900952e-03, 8.675131431052775059e-04, 1.774527815711518676e-05, + 6.162962610875639263e-03, 8.110055490713787008e-03, 4.072629408426510847e-03, 7.298730392435259871e-04, + 1.492980273981122173e-05, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 7.325179555806502624e-03, + 9.639456934591433129e-03, 4.840649467568900952e-03, 8.675131431052775059e-04, 1.774527815711518676e-05, + 8.706568401066729773e-03, 1.145727425666046018e-02, 5.753503429355277983e-03, 1.031109539599387630e-03, + 2.109169842101819183e-05, 7.325179555806502624e-03, 9.639456934591433129e-03, 4.840649467568900952e-03, + 8.675131431052775059e-04, 1.774527815711518676e-05, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 6.162962610875639263e-03, 8.110055490713787008e-03, 4.072629408426510847e-03, 7.298730392435259871e-04, + 1.492980273981122173e-05, 7.325179555806502624e-03, 9.639456934591433129e-03, 4.840649467568900952e-03, + 8.675131431052775059e-04, 1.774527815711518676e-05, 6.162962610875639263e-03, 8.110055490713787008e-03, + 4.072629408426510847e-03, 7.298730392435259871e-04, 1.492980273981122173e-05, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06 +}; + +// ---- order 7: 125 point(s), tdim=3 ---- +inline constexpr int pyramid_o7_npts = 125; +inline constexpr int pyramid_o7_tdim = 3; +inline constexpr double pyramid_o7_points[375] = { + 4.470952170364481115e-02, 4.470952170364481115e-02, 4.691007703066801815e-02, 3.608485692318813554e-02, + 3.608485692318813554e-02, 2.307653449471584461e-01, 2.345503851533400907e-02, 2.345503851533400907e-02, + 5.000000000000000000e-01, 1.082522010747988261e-02, 1.082522010747988261e-02, 7.692346550528414983e-01, + 2.200555327023209599e-03, 2.200555327023209599e-03, 9.530899229693319263e-01, 4.470952170364481115e-02, + 2.199401248396785635e-01, 4.691007703066801815e-02, 3.608485692318813554e-02, 1.775127005185773987e-01, + 2.307653449471584461e-01, 2.345503851533400907e-02, 1.153826724735792231e-01, 5.000000000000000000e-01, + 1.082522010747988261e-02, 5.325264442858104047e-02, 7.692346550528414983e-01, 2.200555327023209599e-03, + 1.082522010747989302e-02, 9.530899229693319263e-01, 4.470952170364481115e-02, 4.765449614846659632e-01, + 4.691007703066801815e-02, 3.608485692318813554e-02, 3.846173275264207492e-01, 2.307653449471584461e-01, + 2.345503851533400907e-02, 2.500000000000000000e-01, 5.000000000000000000e-01, 1.082522010747988261e-02, + 1.153826724735792508e-01, 7.692346550528414983e-01, 2.200555327023209599e-03, 2.345503851533403683e-02, + 9.530899229693319263e-01, 4.470952170364481115e-02, 7.331497981296533073e-01, 4.691007703066801815e-02, + 3.608485692318813554e-02, 5.917219545342640163e-01, 2.307653449471584461e-01, 2.345503851533400907e-02, + 3.846173275264207492e-01, 5.000000000000000000e-01, 1.082522010747988261e-02, 1.775127005185774542e-01, + 7.692346550528414983e-01, 2.200555327023209599e-03, 3.608485692318817717e-02, 9.530899229693319263e-01, + 4.470952170364481115e-02, 9.083804012656870874e-01, 4.691007703066801815e-02, 3.608485692318813554e-02, + 7.331497981296533073e-01, 2.307653449471584461e-01, 2.345503851533400907e-02, 4.765449614846659632e-01, + 5.000000000000000000e-01, 1.082522010747988261e-02, 2.199401248396786190e-01, 7.692346550528414983e-01, + 2.200555327023209599e-03, 4.470952170364485972e-02, 9.530899229693319263e-01, 2.199401248396785635e-01, + 4.470952170364481115e-02, 4.691007703066801815e-02, 1.775127005185773987e-01, 3.608485692318813554e-02, + 2.307653449471584461e-01, 1.153826724735792231e-01, 2.345503851533400907e-02, 5.000000000000000000e-01, + 5.325264442858104047e-02, 1.082522010747988261e-02, 7.692346550528414983e-01, 1.082522010747989302e-02, + 2.200555327023209599e-03, 9.530899229693319263e-01, 2.199401248396785635e-01, 2.199401248396785635e-01, + 4.691007703066801815e-02, 1.775127005185773987e-01, 1.775127005185773987e-01, 2.307653449471584461e-01, + 1.153826724735792231e-01, 1.153826724735792231e-01, 5.000000000000000000e-01, 5.325264442858104047e-02, + 5.325264442858104047e-02, 7.692346550528414983e-01, 1.082522010747989302e-02, 1.082522010747989302e-02, + 9.530899229693319263e-01, 2.199401248396785635e-01, 4.765449614846659632e-01, 4.691007703066801815e-02, + 1.775127005185773987e-01, 3.846173275264207492e-01, 2.307653449471584461e-01, 1.153826724735792231e-01, + 2.500000000000000000e-01, 5.000000000000000000e-01, 5.325264442858104047e-02, 1.153826724735792508e-01, + 7.692346550528414983e-01, 1.082522010747989302e-02, 2.345503851533403683e-02, 9.530899229693319263e-01, + 2.199401248396785635e-01, 7.331497981296533073e-01, 4.691007703066801815e-02, 1.775127005185773987e-01, + 5.917219545342640163e-01, 2.307653449471584461e-01, 1.153826724735792231e-01, 3.846173275264207492e-01, + 5.000000000000000000e-01, 5.325264442858104047e-02, 1.775127005185774542e-01, 7.692346550528414983e-01, + 1.082522010747989302e-02, 3.608485692318817717e-02, 9.530899229693319263e-01, 2.199401248396785635e-01, + 9.083804012656870874e-01, 4.691007703066801815e-02, 1.775127005185773987e-01, 7.331497981296533073e-01, + 2.307653449471584461e-01, 1.153826724735792231e-01, 4.765449614846659632e-01, 5.000000000000000000e-01, + 5.325264442858104047e-02, 2.199401248396786190e-01, 7.692346550528414983e-01, 1.082522010747989302e-02, + 4.470952170364485972e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, 4.470952170364481115e-02, + 4.691007703066801815e-02, 3.846173275264207492e-01, 3.608485692318813554e-02, 2.307653449471584461e-01, + 2.500000000000000000e-01, 2.345503851533400907e-02, 5.000000000000000000e-01, 1.153826724735792508e-01, + 1.082522010747988261e-02, 7.692346550528414983e-01, 2.345503851533403683e-02, 2.200555327023209599e-03, + 9.530899229693319263e-01, 4.765449614846659632e-01, 2.199401248396785635e-01, 4.691007703066801815e-02, + 3.846173275264207492e-01, 1.775127005185773987e-01, 2.307653449471584461e-01, 2.500000000000000000e-01, + 1.153826724735792231e-01, 5.000000000000000000e-01, 1.153826724735792508e-01, 5.325264442858104047e-02, + 7.692346550528414983e-01, 2.345503851533403683e-02, 1.082522010747989302e-02, 9.530899229693319263e-01, + 4.765449614846659632e-01, 4.765449614846659632e-01, 4.691007703066801815e-02, 3.846173275264207492e-01, + 3.846173275264207492e-01, 2.307653449471584461e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 1.153826724735792508e-01, 1.153826724735792508e-01, 7.692346550528414983e-01, + 2.345503851533403683e-02, 2.345503851533403683e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, + 7.331497981296533073e-01, 4.691007703066801815e-02, 3.846173275264207492e-01, 5.917219545342640163e-01, + 2.307653449471584461e-01, 2.500000000000000000e-01, 3.846173275264207492e-01, 5.000000000000000000e-01, + 1.153826724735792508e-01, 1.775127005185774542e-01, 7.692346550528414983e-01, 2.345503851533403683e-02, + 3.608485692318817717e-02, 9.530899229693319263e-01, 4.765449614846659632e-01, 9.083804012656870874e-01, + 4.691007703066801815e-02, 3.846173275264207492e-01, 7.331497981296533073e-01, 2.307653449471584461e-01, + 2.500000000000000000e-01, 4.765449614846659632e-01, 5.000000000000000000e-01, 1.153826724735792508e-01, + 2.199401248396786190e-01, 7.692346550528414983e-01, 2.345503851533403683e-02, 4.470952170364485972e-02, + 9.530899229693319263e-01, 7.331497981296533073e-01, 4.470952170364481115e-02, 4.691007703066801815e-02, + 5.917219545342640163e-01, 3.608485692318813554e-02, 2.307653449471584461e-01, 3.846173275264207492e-01, + 2.345503851533400907e-02, 5.000000000000000000e-01, 1.775127005185774542e-01, 1.082522010747988261e-02, + 7.692346550528414983e-01, 3.608485692318817717e-02, 2.200555327023209599e-03, 9.530899229693319263e-01, + 7.331497981296533073e-01, 2.199401248396785635e-01, 4.691007703066801815e-02, 5.917219545342640163e-01, + 1.775127005185773987e-01, 2.307653449471584461e-01, 3.846173275264207492e-01, 1.153826724735792231e-01, + 5.000000000000000000e-01, 1.775127005185774542e-01, 5.325264442858104047e-02, 7.692346550528414983e-01, + 3.608485692318817717e-02, 1.082522010747989302e-02, 9.530899229693319263e-01, 7.331497981296533073e-01, + 4.765449614846659632e-01, 4.691007703066801815e-02, 5.917219545342640163e-01, 3.846173275264207492e-01, + 2.307653449471584461e-01, 3.846173275264207492e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, + 1.775127005185774542e-01, 1.153826724735792508e-01, 7.692346550528414983e-01, 3.608485692318817717e-02, + 2.345503851533403683e-02, 9.530899229693319263e-01, 7.331497981296533073e-01, 7.331497981296533073e-01, + 4.691007703066801815e-02, 5.917219545342640163e-01, 5.917219545342640163e-01, 2.307653449471584461e-01, + 3.846173275264207492e-01, 3.846173275264207492e-01, 5.000000000000000000e-01, 1.775127005185774542e-01, + 1.775127005185774542e-01, 7.692346550528414983e-01, 3.608485692318817717e-02, 3.608485692318817717e-02, + 9.530899229693319263e-01, 7.331497981296533073e-01, 9.083804012656870874e-01, 4.691007703066801815e-02, + 5.917219545342640163e-01, 7.331497981296533073e-01, 2.307653449471584461e-01, 3.846173275264207492e-01, + 4.765449614846659632e-01, 5.000000000000000000e-01, 1.775127005185774542e-01, 2.199401248396786190e-01, + 7.692346550528414983e-01, 3.608485692318817717e-02, 4.470952170364485972e-02, 9.530899229693319263e-01, + 9.083804012656870874e-01, 4.470952170364481115e-02, 4.691007703066801815e-02, 7.331497981296533073e-01, + 3.608485692318813554e-02, 2.307653449471584461e-01, 4.765449614846659632e-01, 2.345503851533400907e-02, + 5.000000000000000000e-01, 2.199401248396786190e-01, 1.082522010747988261e-02, 7.692346550528414983e-01, + 4.470952170364485972e-02, 2.200555327023209599e-03, 9.530899229693319263e-01, 9.083804012656870874e-01, + 2.199401248396785635e-01, 4.691007703066801815e-02, 7.331497981296533073e-01, 1.775127005185773987e-01, + 2.307653449471584461e-01, 4.765449614846659632e-01, 1.153826724735792231e-01, 5.000000000000000000e-01, + 2.199401248396786190e-01, 5.325264442858104047e-02, 7.692346550528414983e-01, 4.470952170364485972e-02, + 1.082522010747989302e-02, 9.530899229693319263e-01, 9.083804012656870874e-01, 4.765449614846659632e-01, + 4.691007703066801815e-02, 7.331497981296533073e-01, 3.846173275264207492e-01, 2.307653449471584461e-01, + 4.765449614846659632e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, 2.199401248396786190e-01, + 1.153826724735792508e-01, 7.692346550528414983e-01, 4.470952170364485972e-02, 2.345503851533403683e-02, + 9.530899229693319263e-01, 9.083804012656870874e-01, 7.331497981296533073e-01, 4.691007703066801815e-02, + 7.331497981296533073e-01, 5.917219545342640163e-01, 2.307653449471584461e-01, 4.765449614846659632e-01, + 3.846173275264207492e-01, 5.000000000000000000e-01, 2.199401248396786190e-01, 1.775127005185774542e-01, + 7.692346550528414983e-01, 4.470952170364485972e-02, 3.608485692318817717e-02, 9.530899229693319263e-01, + 9.083804012656870874e-01, 9.083804012656870874e-01, 4.691007703066801815e-02, 7.331497981296533073e-01, + 7.331497981296533073e-01, 2.307653449471584461e-01, 4.765449614846659632e-01, 4.765449614846659632e-01, + 5.000000000000000000e-01, 2.199401248396786190e-01, 2.199401248396786190e-01, 7.692346550528414983e-01, + 4.470952170364485972e-02, 4.470952170364485972e-02, 9.530899229693319263e-01 +}; +inline constexpr double pyramid_o7_weights[125] = { + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 6.162962610875639263e-03, 8.110055490713787008e-03, + 4.072629408426510847e-03, 7.298730392435259871e-04, 1.492980273981122173e-05, 7.325179555806502624e-03, + 9.639456934591433129e-03, 4.840649467568900952e-03, 8.675131431052775059e-04, 1.774527815711518676e-05, + 6.162962610875639263e-03, 8.110055490713787008e-03, 4.072629408426510847e-03, 7.298730392435259871e-04, + 1.492980273981122173e-05, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 7.325179555806502624e-03, + 9.639456934591433129e-03, 4.840649467568900952e-03, 8.675131431052775059e-04, 1.774527815711518676e-05, + 8.706568401066729773e-03, 1.145727425666046018e-02, 5.753503429355277983e-03, 1.031109539599387630e-03, + 2.109169842101819183e-05, 7.325179555806502624e-03, 9.639456934591433129e-03, 4.840649467568900952e-03, + 8.675131431052775059e-04, 1.774527815711518676e-05, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 6.162962610875639263e-03, 8.110055490713787008e-03, 4.072629408426510847e-03, 7.298730392435259871e-04, + 1.492980273981122173e-05, 7.325179555806502624e-03, 9.639456934591433129e-03, 4.840649467568900952e-03, + 8.675131431052775059e-04, 1.774527815711518676e-05, 6.162962610875639263e-03, 8.110055490713787008e-03, + 4.072629408426510847e-03, 7.298730392435259871e-04, 1.492980273981122173e-05, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06, 3.050739799162231845e-03, 4.014573934827006182e-03, 2.016000000000000431e-03, + 3.612958360685814756e-04, 7.390429942185235926e-06, 3.626051011160248434e-03, 4.771645841364306057e-03, + 2.396179064667037367e-03, 4.294293246720777405e-04, 8.784123763071767629e-06, 3.050739799162231845e-03, + 4.014573934827006182e-03, 2.016000000000000431e-03, 3.612958360685814756e-04, 7.390429942185235926e-06, + 1.510152488312770276e-03, 1.987261850014038472e-03, 9.979439797765091071e-04, 1.788457363705165510e-04, + 3.658350728553476718e-06 +}; + +// ---- order 8: 216 point(s), tdim=3 ---- +inline constexpr int pyramid_o8_npts = 216; +inline constexpr int pyramid_o8_tdim = 3; +inline constexpr double pyramid_o8_points[648] = { + 3.262515127043440211e-02, 3.262515127043440211e-02, 3.376524289842397497e-02, 2.804556921958764604e-02, + 2.804556921958764604e-02, 1.693953067668677037e-01, 2.091113883837367529e-02, 2.091113883837367529e-02, + 3.806904069584015060e-01, 1.285410406005029968e-02, 1.285410406005029968e-02, 6.193095930415984940e-01, + 5.719673678836328934e-03, 5.719673678836328934e-03, 8.306046932331323518e-01, 1.140091627989570696e-03, + 1.140091627989570696e-03, 9.662347571015760250e-01, 3.262515127043440211e-02, 1.636756330880313748e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 1.407005368122264954e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 1.049081384969455799e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 6.448716826992212381e-02, 6.193095930415984940e-01, 5.719673678836328934e-03, 2.869476995464120481e-02, + 8.306046932331323518e-01, 1.140091627989570696e-03, 5.719673678836330669e-03, 9.662347571015760250e-01, + 3.262515127043440211e-02, 3.678363028983512306e-01, 3.376524289842397497e-02, 2.804556921958764604e-02, + 3.162032386884793822e-01, 1.693953067668677037e-01, 2.091113883837367529e-02, 2.357652210082481492e-01, + 3.806904069584015060e-01, 1.285410406005029968e-02, 1.449251859501533568e-01, 6.193095930415984940e-01, + 5.719673678836328934e-03, 6.448716826992210993e-02, 8.306046932331323518e-01, 1.140091627989570696e-03, + 1.285410406005029968e-02, 9.662347571015760250e-01, 3.262515127043440211e-02, 5.983984542032247944e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 5.144014545446529141e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 3.835443720333503448e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 2.357652210082481492e-01, 6.193095930415984940e-01, 5.719673678836328934e-03, 1.049081384969455383e-01, + 8.306046932331323518e-01, 1.140091627989570696e-03, 2.091113883837367529e-02, 9.662347571015760250e-01, + 3.262515127043440211e-02, 8.025591240135446780e-01, 3.376524289842397497e-02, 2.804556921958764604e-02, + 6.899041564209058564e-01, 1.693953067668677037e-01, 2.091113883837367529e-02, 5.144014545446529141e-01, + 3.806904069584015060e-01, 1.285410406005029968e-02, 3.162032386884793822e-01, 6.193095930415984940e-01, + 5.719673678836328934e-03, 1.407005368122264399e-01, 8.306046932331323518e-01, 1.140091627989570696e-03, + 2.804556921958764604e-02, 9.662347571015760250e-01, 3.262515127043440211e-02, 9.336096058311416090e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 8.025591240135446780e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 5.983984542032247944e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 3.678363028983512306e-01, 6.193095930415984940e-01, 5.719673678836328934e-03, 1.636756330880313193e-01, + 8.306046932331323518e-01, 1.140091627989570696e-03, 3.262515127043440211e-02, 9.662347571015760250e-01, + 1.636756330880313748e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 1.407005368122264954e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 1.049081384969455799e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 6.448716826992212381e-02, 1.285410406005029968e-02, 6.193095930415984940e-01, + 2.869476995464120481e-02, 5.719673678836328934e-03, 8.306046932331323518e-01, 5.719673678836330669e-03, + 1.140091627989570696e-03, 9.662347571015760250e-01, 1.636756330880313748e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 6.448716826992212381e-02, 6.193095930415984940e-01, 2.869476995464120481e-02, 2.869476995464120481e-02, + 8.306046932331323518e-01, 5.719673678836330669e-03, 5.719673678836330669e-03, 9.662347571015760250e-01, + 1.636756330880313748e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 1.407005368122264954e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 1.049081384969455799e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 6.448716826992212381e-02, 1.449251859501533568e-01, 6.193095930415984940e-01, + 2.869476995464120481e-02, 6.448716826992210993e-02, 8.306046932331323518e-01, 5.719673678836330669e-03, + 1.285410406005029968e-02, 9.662347571015760250e-01, 1.636756330880313748e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 2.357652210082481492e-01, 6.193095930415984940e-01, 2.869476995464120481e-02, 1.049081384969455383e-01, + 8.306046932331323518e-01, 5.719673678836330669e-03, 2.091113883837367529e-02, 9.662347571015760250e-01, + 1.636756330880313748e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 1.407005368122264954e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 1.049081384969455799e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 6.448716826992212381e-02, 3.162032386884793822e-01, 6.193095930415984940e-01, + 2.869476995464120481e-02, 1.407005368122264399e-01, 8.306046932331323518e-01, 5.719673678836330669e-03, + 2.804556921958764604e-02, 9.662347571015760250e-01, 1.636756330880313748e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 3.678363028983512306e-01, 6.193095930415984940e-01, 2.869476995464120481e-02, 1.636756330880313193e-01, + 8.306046932331323518e-01, 5.719673678836330669e-03, 3.262515127043440211e-02, 9.662347571015760250e-01, + 3.678363028983512306e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 3.162032386884793822e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 2.357652210082481492e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 1.449251859501533568e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 6.448716826992210993e-02, 5.719673678836328934e-03, 8.306046932331323518e-01, 1.285410406005029968e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 3.678363028983512306e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 6.448716826992210993e-02, 2.869476995464120481e-02, + 8.306046932331323518e-01, 1.285410406005029968e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 3.678363028983512306e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 3.162032386884793822e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 2.357652210082481492e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 1.449251859501533568e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 6.448716826992210993e-02, 6.448716826992210993e-02, 8.306046932331323518e-01, 1.285410406005029968e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 3.678363028983512306e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 6.448716826992210993e-02, 1.049081384969455383e-01, + 8.306046932331323518e-01, 1.285410406005029968e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 3.678363028983512306e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 3.162032386884793822e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 2.357652210082481492e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 1.449251859501533568e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 6.448716826992210993e-02, 1.407005368122264399e-01, 8.306046932331323518e-01, 1.285410406005029968e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 3.678363028983512306e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 6.448716826992210993e-02, 1.636756330880313193e-01, + 8.306046932331323518e-01, 1.285410406005029968e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 5.983984542032247944e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 5.144014545446529141e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 3.835443720333503448e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 2.357652210082481492e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.049081384969455383e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 2.091113883837367529e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 5.983984542032247944e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.049081384969455383e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 2.091113883837367529e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 5.983984542032247944e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 5.144014545446529141e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 3.835443720333503448e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 2.357652210082481492e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.049081384969455383e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 2.091113883837367529e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 5.983984542032247944e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.049081384969455383e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 2.091113883837367529e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 5.983984542032247944e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 5.144014545446529141e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 3.835443720333503448e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 2.357652210082481492e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.049081384969455383e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 2.091113883837367529e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 5.983984542032247944e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.049081384969455383e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 2.091113883837367529e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 8.025591240135446780e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 6.899041564209058564e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 5.144014545446529141e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 3.162032386884793822e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.407005368122264399e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 2.804556921958764604e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 8.025591240135446780e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.407005368122264399e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 2.804556921958764604e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 8.025591240135446780e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 6.899041564209058564e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 5.144014545446529141e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 3.162032386884793822e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.407005368122264399e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 2.804556921958764604e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 8.025591240135446780e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.407005368122264399e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 2.804556921958764604e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 8.025591240135446780e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 6.899041564209058564e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 5.144014545446529141e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 3.162032386884793822e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.407005368122264399e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 2.804556921958764604e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 8.025591240135446780e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.407005368122264399e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 2.804556921958764604e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 9.336096058311416090e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 8.025591240135446780e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 5.983984542032247944e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 3.678363028983512306e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.636756330880313193e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 3.262515127043440211e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 9.336096058311416090e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.636756330880313193e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 3.262515127043440211e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 9.336096058311416090e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 8.025591240135446780e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 5.983984542032247944e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 3.678363028983512306e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.636756330880313193e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 3.262515127043440211e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 9.336096058311416090e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.636756330880313193e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 3.262515127043440211e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 9.336096058311416090e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 8.025591240135446780e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 5.983984542032247944e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 3.678363028983512306e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.636756330880313193e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 3.262515127043440211e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 9.336096058311416090e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.636756330880313193e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 3.262515127043440211e-02, 3.262515127043440211e-02, 9.662347571015760250e-01 +}; +inline constexpr double pyramid_o8_weights[216] = { + 5.868588869827762872e-04, 9.131832855597217307e-04, 6.584616920495327480e-04, 2.488048062220904293e-04, + 3.798148490871391193e-05, 7.166516921863735577e-07, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 5.868588869827762872e-04, 9.131832855597217307e-04, + 6.584616920495327480e-04, 2.488048062220904293e-04, 3.798148490871391193e-05, 7.166516921863735577e-07, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 2.602167941369070651e-03, 4.049110140420053051e-03, + 2.919659127052147678e-03, 1.103215619240795631e-03, 1.684122104773531025e-04, 3.177677121195249137e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 2.602167941369070651e-03, 4.049110140420053051e-03, 2.919659127052147678e-03, 1.103215619240795631e-03, + 1.684122104773531025e-04, 3.177677121195249137e-06, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 3.375056355302152693e-03, 5.251765151465610272e-03, + 3.786847856902281640e-03, 1.430889539369337680e-03, 2.184335193150613861e-04, 4.121501611208709204e-06, + 4.377505855933471086e-03, 6.811629283881954870e-03, 4.911606481200459472e-03, 1.855888221819769132e-03, + 2.833120129777017319e-04, 5.345658128037438073e-06, 4.377505855933468484e-03, 6.811629283881949666e-03, + 4.911606481200456002e-03, 1.855888221819768048e-03, 2.833120129777015150e-04, 5.345658128037433837e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 1.602803236324940386e-03, 2.494046112137892918e-03, + 1.798361675051130069e-03, 6.795247673188430483e-04, 1.037333646681237189e-04, 1.957287649607964959e-06, + 1.602803236324939302e-03, 2.494046112137891184e-03, 1.798361675051128768e-03, 6.795247673188425062e-04, + 1.037333646681236511e-04, 1.957287649607963689e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 4.377505855933468484e-03, 6.811629283881949666e-03, 4.911606481200456002e-03, 1.855888221819768048e-03, + 2.833120129777015150e-04, 5.345658128037433837e-06, 4.377505855933466750e-03, 6.811629283881946197e-03, + 4.911606481200454268e-03, 1.855888221819766964e-03, 2.833120129777014066e-04, 5.345658128037432143e-06, + 3.375056355302150958e-03, 5.251765151465607670e-03, 3.786847856902279905e-03, 1.430889539369337030e-03, + 2.184335193150612777e-04, 4.121501611208707510e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 2.602167941369070651e-03, 4.049110140420053051e-03, + 2.919659127052147678e-03, 1.103215619240795631e-03, 1.684122104773531025e-04, 3.177677121195249137e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 2.602167941369070651e-03, 4.049110140420053051e-03, 2.919659127052147678e-03, 1.103215619240795631e-03, + 1.684122104773531025e-04, 3.177677121195249137e-06, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 5.868588869827762872e-04, 9.131832855597217307e-04, 6.584616920495327480e-04, 2.488048062220904293e-04, + 3.798148490871391193e-05, 7.166516921863735577e-07, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 5.868588869827762872e-04, 9.131832855597217307e-04, + 6.584616920495327480e-04, 2.488048062220904293e-04, 3.798148490871391193e-05, 7.166516921863735577e-07 +}; + +// ---- order 9: 216 point(s), tdim=3 ---- +inline constexpr int pyramid_o9_npts = 216; +inline constexpr int pyramid_o9_tdim = 3; +inline constexpr double pyramid_o9_points[648] = { + 3.262515127043440211e-02, 3.262515127043440211e-02, 3.376524289842397497e-02, 2.804556921958764604e-02, + 2.804556921958764604e-02, 1.693953067668677037e-01, 2.091113883837367529e-02, 2.091113883837367529e-02, + 3.806904069584015060e-01, 1.285410406005029968e-02, 1.285410406005029968e-02, 6.193095930415984940e-01, + 5.719673678836328934e-03, 5.719673678836328934e-03, 8.306046932331323518e-01, 1.140091627989570696e-03, + 1.140091627989570696e-03, 9.662347571015760250e-01, 3.262515127043440211e-02, 1.636756330880313748e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 1.407005368122264954e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 1.049081384969455799e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 6.448716826992212381e-02, 6.193095930415984940e-01, 5.719673678836328934e-03, 2.869476995464120481e-02, + 8.306046932331323518e-01, 1.140091627989570696e-03, 5.719673678836330669e-03, 9.662347571015760250e-01, + 3.262515127043440211e-02, 3.678363028983512306e-01, 3.376524289842397497e-02, 2.804556921958764604e-02, + 3.162032386884793822e-01, 1.693953067668677037e-01, 2.091113883837367529e-02, 2.357652210082481492e-01, + 3.806904069584015060e-01, 1.285410406005029968e-02, 1.449251859501533568e-01, 6.193095930415984940e-01, + 5.719673678836328934e-03, 6.448716826992210993e-02, 8.306046932331323518e-01, 1.140091627989570696e-03, + 1.285410406005029968e-02, 9.662347571015760250e-01, 3.262515127043440211e-02, 5.983984542032247944e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 5.144014545446529141e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 3.835443720333503448e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 2.357652210082481492e-01, 6.193095930415984940e-01, 5.719673678836328934e-03, 1.049081384969455383e-01, + 8.306046932331323518e-01, 1.140091627989570696e-03, 2.091113883837367529e-02, 9.662347571015760250e-01, + 3.262515127043440211e-02, 8.025591240135446780e-01, 3.376524289842397497e-02, 2.804556921958764604e-02, + 6.899041564209058564e-01, 1.693953067668677037e-01, 2.091113883837367529e-02, 5.144014545446529141e-01, + 3.806904069584015060e-01, 1.285410406005029968e-02, 3.162032386884793822e-01, 6.193095930415984940e-01, + 5.719673678836328934e-03, 1.407005368122264399e-01, 8.306046932331323518e-01, 1.140091627989570696e-03, + 2.804556921958764604e-02, 9.662347571015760250e-01, 3.262515127043440211e-02, 9.336096058311416090e-01, + 3.376524289842397497e-02, 2.804556921958764604e-02, 8.025591240135446780e-01, 1.693953067668677037e-01, + 2.091113883837367529e-02, 5.983984542032247944e-01, 3.806904069584015060e-01, 1.285410406005029968e-02, + 3.678363028983512306e-01, 6.193095930415984940e-01, 5.719673678836328934e-03, 1.636756330880313193e-01, + 8.306046932331323518e-01, 1.140091627989570696e-03, 3.262515127043440211e-02, 9.662347571015760250e-01, + 1.636756330880313748e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 1.407005368122264954e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 1.049081384969455799e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 6.448716826992212381e-02, 1.285410406005029968e-02, 6.193095930415984940e-01, + 2.869476995464120481e-02, 5.719673678836328934e-03, 8.306046932331323518e-01, 5.719673678836330669e-03, + 1.140091627989570696e-03, 9.662347571015760250e-01, 1.636756330880313748e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 6.448716826992212381e-02, 6.193095930415984940e-01, 2.869476995464120481e-02, 2.869476995464120481e-02, + 8.306046932331323518e-01, 5.719673678836330669e-03, 5.719673678836330669e-03, 9.662347571015760250e-01, + 1.636756330880313748e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 1.407005368122264954e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 1.049081384969455799e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 6.448716826992212381e-02, 1.449251859501533568e-01, 6.193095930415984940e-01, + 2.869476995464120481e-02, 6.448716826992210993e-02, 8.306046932331323518e-01, 5.719673678836330669e-03, + 1.285410406005029968e-02, 9.662347571015760250e-01, 1.636756330880313748e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 2.357652210082481492e-01, 6.193095930415984940e-01, 2.869476995464120481e-02, 1.049081384969455383e-01, + 8.306046932331323518e-01, 5.719673678836330669e-03, 2.091113883837367529e-02, 9.662347571015760250e-01, + 1.636756330880313748e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 1.407005368122264954e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 1.049081384969455799e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 6.448716826992212381e-02, 3.162032386884793822e-01, 6.193095930415984940e-01, + 2.869476995464120481e-02, 1.407005368122264399e-01, 8.306046932331323518e-01, 5.719673678836330669e-03, + 2.804556921958764604e-02, 9.662347571015760250e-01, 1.636756330880313748e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 1.407005368122264954e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 1.049081384969455799e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 6.448716826992212381e-02, + 3.678363028983512306e-01, 6.193095930415984940e-01, 2.869476995464120481e-02, 1.636756330880313193e-01, + 8.306046932331323518e-01, 5.719673678836330669e-03, 3.262515127043440211e-02, 9.662347571015760250e-01, + 3.678363028983512306e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 3.162032386884793822e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 2.357652210082481492e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 1.449251859501533568e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 6.448716826992210993e-02, 5.719673678836328934e-03, 8.306046932331323518e-01, 1.285410406005029968e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 3.678363028983512306e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 6.448716826992210993e-02, 2.869476995464120481e-02, + 8.306046932331323518e-01, 1.285410406005029968e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 3.678363028983512306e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 3.162032386884793822e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 2.357652210082481492e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 1.449251859501533568e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 6.448716826992210993e-02, 6.448716826992210993e-02, 8.306046932331323518e-01, 1.285410406005029968e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 3.678363028983512306e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 6.448716826992210993e-02, 1.049081384969455383e-01, + 8.306046932331323518e-01, 1.285410406005029968e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 3.678363028983512306e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 3.162032386884793822e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 2.357652210082481492e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 1.449251859501533568e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 6.448716826992210993e-02, 1.407005368122264399e-01, 8.306046932331323518e-01, 1.285410406005029968e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 3.678363028983512306e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 3.162032386884793822e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 2.357652210082481492e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 1.449251859501533568e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 6.448716826992210993e-02, 1.636756330880313193e-01, + 8.306046932331323518e-01, 1.285410406005029968e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 5.983984542032247944e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 5.144014545446529141e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 3.835443720333503448e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 2.357652210082481492e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.049081384969455383e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 2.091113883837367529e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 5.983984542032247944e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.049081384969455383e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 2.091113883837367529e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 5.983984542032247944e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 5.144014545446529141e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 3.835443720333503448e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 2.357652210082481492e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.049081384969455383e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 2.091113883837367529e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 5.983984542032247944e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.049081384969455383e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 2.091113883837367529e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 5.983984542032247944e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 5.144014545446529141e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 3.835443720333503448e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 2.357652210082481492e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.049081384969455383e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 2.091113883837367529e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 5.983984542032247944e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 5.144014545446529141e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 3.835443720333503448e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 2.357652210082481492e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.049081384969455383e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 2.091113883837367529e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 8.025591240135446780e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 6.899041564209058564e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 5.144014545446529141e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 3.162032386884793822e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.407005368122264399e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 2.804556921958764604e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 8.025591240135446780e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.407005368122264399e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 2.804556921958764604e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 8.025591240135446780e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 6.899041564209058564e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 5.144014545446529141e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 3.162032386884793822e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.407005368122264399e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 2.804556921958764604e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 8.025591240135446780e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.407005368122264399e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 2.804556921958764604e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 8.025591240135446780e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 6.899041564209058564e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 5.144014545446529141e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 3.162032386884793822e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.407005368122264399e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 2.804556921958764604e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 8.025591240135446780e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 6.899041564209058564e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 5.144014545446529141e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 3.162032386884793822e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.407005368122264399e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 2.804556921958764604e-02, 3.262515127043440211e-02, 9.662347571015760250e-01, + 9.336096058311416090e-01, 3.262515127043440211e-02, 3.376524289842397497e-02, 8.025591240135446780e-01, + 2.804556921958764604e-02, 1.693953067668677037e-01, 5.983984542032247944e-01, 2.091113883837367529e-02, + 3.806904069584015060e-01, 3.678363028983512306e-01, 1.285410406005029968e-02, 6.193095930415984940e-01, + 1.636756330880313193e-01, 5.719673678836328934e-03, 8.306046932331323518e-01, 3.262515127043440211e-02, + 1.140091627989570696e-03, 9.662347571015760250e-01, 9.336096058311416090e-01, 1.636756330880313748e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 1.407005368122264954e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 1.049081384969455799e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 6.448716826992212381e-02, 6.193095930415984940e-01, 1.636756330880313193e-01, 2.869476995464120481e-02, + 8.306046932331323518e-01, 3.262515127043440211e-02, 5.719673678836330669e-03, 9.662347571015760250e-01, + 9.336096058311416090e-01, 3.678363028983512306e-01, 3.376524289842397497e-02, 8.025591240135446780e-01, + 3.162032386884793822e-01, 1.693953067668677037e-01, 5.983984542032247944e-01, 2.357652210082481492e-01, + 3.806904069584015060e-01, 3.678363028983512306e-01, 1.449251859501533568e-01, 6.193095930415984940e-01, + 1.636756330880313193e-01, 6.448716826992210993e-02, 8.306046932331323518e-01, 3.262515127043440211e-02, + 1.285410406005029968e-02, 9.662347571015760250e-01, 9.336096058311416090e-01, 5.983984542032247944e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 5.144014545446529141e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 3.835443720333503448e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 2.357652210082481492e-01, 6.193095930415984940e-01, 1.636756330880313193e-01, 1.049081384969455383e-01, + 8.306046932331323518e-01, 3.262515127043440211e-02, 2.091113883837367529e-02, 9.662347571015760250e-01, + 9.336096058311416090e-01, 8.025591240135446780e-01, 3.376524289842397497e-02, 8.025591240135446780e-01, + 6.899041564209058564e-01, 1.693953067668677037e-01, 5.983984542032247944e-01, 5.144014545446529141e-01, + 3.806904069584015060e-01, 3.678363028983512306e-01, 3.162032386884793822e-01, 6.193095930415984940e-01, + 1.636756330880313193e-01, 1.407005368122264399e-01, 8.306046932331323518e-01, 3.262515127043440211e-02, + 2.804556921958764604e-02, 9.662347571015760250e-01, 9.336096058311416090e-01, 9.336096058311416090e-01, + 3.376524289842397497e-02, 8.025591240135446780e-01, 8.025591240135446780e-01, 1.693953067668677037e-01, + 5.983984542032247944e-01, 5.983984542032247944e-01, 3.806904069584015060e-01, 3.678363028983512306e-01, + 3.678363028983512306e-01, 6.193095930415984940e-01, 1.636756330880313193e-01, 1.636756330880313193e-01, + 8.306046932331323518e-01, 3.262515127043440211e-02, 3.262515127043440211e-02, 9.662347571015760250e-01 +}; +inline constexpr double pyramid_o9_weights[216] = { + 5.868588869827762872e-04, 9.131832855597217307e-04, 6.584616920495327480e-04, 2.488048062220904293e-04, + 3.798148490871391193e-05, 7.166516921863735577e-07, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 5.868588869827762872e-04, 9.131832855597217307e-04, + 6.584616920495327480e-04, 2.488048062220904293e-04, 3.798148490871391193e-05, 7.166516921863735577e-07, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 2.602167941369070651e-03, 4.049110140420053051e-03, + 2.919659127052147678e-03, 1.103215619240795631e-03, 1.684122104773531025e-04, 3.177677121195249137e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 2.602167941369070651e-03, 4.049110140420053051e-03, 2.919659127052147678e-03, 1.103215619240795631e-03, + 1.684122104773531025e-04, 3.177677121195249137e-06, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 3.375056355302152693e-03, 5.251765151465610272e-03, + 3.786847856902281640e-03, 1.430889539369337680e-03, 2.184335193150613861e-04, 4.121501611208709204e-06, + 4.377505855933471086e-03, 6.811629283881954870e-03, 4.911606481200459472e-03, 1.855888221819769132e-03, + 2.833120129777017319e-04, 5.345658128037438073e-06, 4.377505855933468484e-03, 6.811629283881949666e-03, + 4.911606481200456002e-03, 1.855888221819768048e-03, 2.833120129777015150e-04, 5.345658128037433837e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 1.602803236324940386e-03, 2.494046112137892918e-03, + 1.798361675051130069e-03, 6.795247673188430483e-04, 1.037333646681237189e-04, 1.957287649607964959e-06, + 1.602803236324939302e-03, 2.494046112137891184e-03, 1.798361675051128768e-03, 6.795247673188425062e-04, + 1.037333646681236511e-04, 1.957287649607963689e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 4.377505855933468484e-03, 6.811629283881949666e-03, 4.911606481200456002e-03, 1.855888221819768048e-03, + 2.833120129777015150e-04, 5.345658128037433837e-06, 4.377505855933466750e-03, 6.811629283881946197e-03, + 4.911606481200454268e-03, 1.855888221819766964e-03, 2.833120129777014066e-04, 5.345658128037432143e-06, + 3.375056355302150958e-03, 5.251765151465607670e-03, 3.786847856902279905e-03, 1.430889539369337030e-03, + 2.184335193150612777e-04, 4.121501611208707510e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 2.602167941369070651e-03, 4.049110140420053051e-03, + 2.919659127052147678e-03, 1.103215619240795631e-03, 1.684122104773531025e-04, 3.177677121195249137e-06, + 3.375056355302152693e-03, 5.251765151465610272e-03, 3.786847856902281640e-03, 1.430889539369337680e-03, + 2.184335193150613861e-04, 4.121501611208709204e-06, 3.375056355302150958e-03, 5.251765151465607670e-03, + 3.786847856902279905e-03, 1.430889539369337030e-03, 2.184335193150612777e-04, 4.121501611208707510e-06, + 2.602167941369070651e-03, 4.049110140420053051e-03, 2.919659127052147678e-03, 1.103215619240795631e-03, + 1.684122104773531025e-04, 3.177677121195249137e-06, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 5.868588869827762872e-04, 9.131832855597217307e-04, 6.584616920495327480e-04, 2.488048062220904293e-04, + 3.798148490871391193e-05, 7.166516921863735577e-07, 1.235761053688825021e-03, 1.922909176644068937e-03, + 1.386536580479079579e-03, 5.239134932089359147e-04, 7.997840852810675523e-05, 1.509068482914708443e-06, + 1.602803236324940386e-03, 2.494046112137892918e-03, 1.798361675051130069e-03, 6.795247673188430483e-04, + 1.037333646681237189e-04, 1.957287649607964959e-06, 1.602803236324939302e-03, 2.494046112137891184e-03, + 1.798361675051128768e-03, 6.795247673188425062e-04, 1.037333646681236511e-04, 1.957287649607963689e-06, + 1.235761053688825021e-03, 1.922909176644068937e-03, 1.386536580479079579e-03, 5.239134932089359147e-04, + 7.997840852810675523e-05, 1.509068482914708443e-06, 5.868588869827762872e-04, 9.131832855597217307e-04, + 6.584616920495327480e-04, 2.488048062220904293e-04, 3.798148490871391193e-05, 7.166516921863735577e-07 +}; + +// ---- order 10: 343 point(s), tdim=3 ---- +inline constexpr int pyramid_o10_npts = 343; +inline constexpr int pyramid_o10_tdim = 3; +inline constexpr double pyramid_o10_points[1029] = { + 2.479854268209261484e-02, 2.479854268209261484e-02, 2.544604382862070135e-02, 2.215753943883598184e-02, + 2.215753943883598184e-02, 1.292344072003027700e-01, 1.788659866910157631e-02, 1.788659866910157631e-02, + 2.970774243113014079e-01, 1.272302191431035068e-02, 1.272302191431035068e-02, 5.000000000000000000e-01, + 7.559445159519125913e-03, 7.559445159519125913e-03, 7.029225756886985366e-01, 3.288504389784719078e-03, + 3.288504389784719078e-03, 8.707655927996972300e-01, 6.475011465280884702e-04, 6.475011465280884702e-04, + 9.745539561713791876e-01, 2.479854268209261484e-02, 1.259459028105180400e-01, 2.544604382862070135e-02, + 2.215753943883598184e-02, 1.125328751958891010e-01, 1.292344072003027700e-01, 1.788659866910157631e-02, + 9.084178237683891710e-02, 2.970774243113014079e-01, 1.272302191431035068e-02, 6.461720360015138498e-02, + 5.000000000000000000e-01, 7.559445159519125913e-03, 3.839262482346385980e-02, 7.029225756886985366e-01, + 3.288504389784719078e-03, 1.670153200441366892e-02, 8.707655927996972300e-01, 6.475011465280884702e-04, + 3.288504389784733389e-03, 9.745539561713791876e-01, 2.479854268209261484e-02, 2.895179791517822898e-01, + 2.544604382862070135e-02, 2.215753943883598184e-02, 2.586847994878375689e-01, 1.292344072003027700e-01, + 1.788659866910157631e-02, 2.088224282758643635e-01, 2.970774243113014079e-01, 1.272302191431035068e-02, + 1.485387121556507040e-01, 5.000000000000000000e-01, 7.559445159519125913e-03, 8.825499603543703053e-02, + 7.029225756886985366e-01, 3.288504389784719078e-03, 3.839262482346385286e-02, 8.707655927996972300e-01, + 6.475011465280884702e-04, 7.559445159519158006e-03, 9.745539561713791876e-01, 2.479854268209261484e-02, + 4.872769780856896493e-01, 2.544604382862070135e-02, 2.215753943883598184e-02, 4.353827963998486150e-01, + 1.292344072003027700e-01, 1.788659866910157631e-02, 3.514612878443492683e-01, 2.970774243113014079e-01, + 1.272302191431035068e-02, 2.500000000000000000e-01, 5.000000000000000000e-01, 7.559445159519125913e-03, + 1.485387121556507317e-01, 7.029225756886985366e-01, 3.288504389784719078e-03, 6.461720360015138498e-02, + 8.707655927996972300e-01, 6.475011465280884702e-04, 1.272302191431040619e-02, 9.745539561713791876e-01, + 2.479854268209261484e-02, 6.850359770195969533e-01, 2.544604382862070135e-02, 2.215753943883598184e-02, + 6.120807933118596056e-01, 1.292344072003027700e-01, 1.788659866910157631e-02, 4.941001474128341453e-01, + 2.970774243113014079e-01, 1.272302191431035068e-02, 3.514612878443492683e-01, 5.000000000000000000e-01, + 7.559445159519125913e-03, 2.088224282758644190e-01, 7.029225756886985366e-01, 3.288504389784719078e-03, + 9.084178237683891710e-02, 8.707655927996972300e-01, 6.475011465280884702e-04, 1.788659866910165264e-02, + 9.745539561713791876e-01, 2.479854268209261484e-02, 8.486080533608612031e-01, 2.544604382862070135e-02, + 2.215753943883598184e-02, 7.582327176038081706e-01, 1.292344072003027700e-01, 1.788659866910157631e-02, + 6.120807933118596056e-01, 2.970774243113014079e-01, 1.272302191431035068e-02, 4.353827963998486150e-01, + 5.000000000000000000e-01, 7.559445159519125913e-03, 2.586847994878376245e-01, 7.029225756886985366e-01, + 3.288504389784719078e-03, 1.125328751958891010e-01, 8.707655927996972300e-01, 6.475011465280884702e-04, + 2.215753943883607899e-02, 9.745539561713791876e-01, 2.479854268209261484e-02, 9.497554134892866040e-01, + 2.544604382862070135e-02, 2.215753943883598184e-02, 8.486080533608612031e-01, 1.292344072003027700e-01, + 1.788659866910157631e-02, 6.850359770195968423e-01, 2.970774243113014079e-01, 1.272302191431035068e-02, + 4.872769780856895938e-01, 5.000000000000000000e-01, 7.559445159519125913e-03, 2.895179791517822898e-01, + 7.029225756886985366e-01, 3.288504389784719078e-03, 1.259459028105180400e-01, 8.707655927996972300e-01, + 6.475011465280884702e-04, 2.479854268209272239e-02, 9.745539561713791876e-01, 1.259459028105180400e-01, + 2.479854268209261484e-02, 2.544604382862070135e-02, 1.125328751958891010e-01, 2.215753943883598184e-02, + 1.292344072003027700e-01, 9.084178237683891710e-02, 1.788659866910157631e-02, 2.970774243113014079e-01, + 6.461720360015138498e-02, 1.272302191431035068e-02, 5.000000000000000000e-01, 3.839262482346385980e-02, + 7.559445159519125913e-03, 7.029225756886985366e-01, 1.670153200441366892e-02, 3.288504389784719078e-03, + 8.707655927996972300e-01, 3.288504389784733389e-03, 6.475011465280884702e-04, 9.745539561713791876e-01, + 1.259459028105180400e-01, 1.259459028105180400e-01, 2.544604382862070135e-02, 1.125328751958891010e-01, + 1.125328751958891010e-01, 1.292344072003027700e-01, 9.084178237683891710e-02, 9.084178237683891710e-02, + 2.970774243113014079e-01, 6.461720360015138498e-02, 6.461720360015138498e-02, 5.000000000000000000e-01, + 3.839262482346385980e-02, 3.839262482346385980e-02, 7.029225756886985366e-01, 1.670153200441366892e-02, + 1.670153200441366892e-02, 8.707655927996972300e-01, 3.288504389784733389e-03, 3.288504389784733389e-03, + 9.745539561713791876e-01, 1.259459028105180400e-01, 2.895179791517822898e-01, 2.544604382862070135e-02, + 1.125328751958891010e-01, 2.586847994878375689e-01, 1.292344072003027700e-01, 9.084178237683891710e-02, + 2.088224282758643635e-01, 2.970774243113014079e-01, 6.461720360015138498e-02, 1.485387121556507040e-01, + 5.000000000000000000e-01, 3.839262482346385980e-02, 8.825499603543703053e-02, 7.029225756886985366e-01, + 1.670153200441366892e-02, 3.839262482346385286e-02, 8.707655927996972300e-01, 3.288504389784733389e-03, + 7.559445159519158006e-03, 9.745539561713791876e-01, 1.259459028105180400e-01, 4.872769780856896493e-01, + 2.544604382862070135e-02, 1.125328751958891010e-01, 4.353827963998486150e-01, 1.292344072003027700e-01, + 9.084178237683891710e-02, 3.514612878443492683e-01, 2.970774243113014079e-01, 6.461720360015138498e-02, + 2.500000000000000000e-01, 5.000000000000000000e-01, 3.839262482346385980e-02, 1.485387121556507317e-01, + 7.029225756886985366e-01, 1.670153200441366892e-02, 6.461720360015138498e-02, 8.707655927996972300e-01, + 3.288504389784733389e-03, 1.272302191431040619e-02, 9.745539561713791876e-01, 1.259459028105180400e-01, + 6.850359770195969533e-01, 2.544604382862070135e-02, 1.125328751958891010e-01, 6.120807933118596056e-01, + 1.292344072003027700e-01, 9.084178237683891710e-02, 4.941001474128341453e-01, 2.970774243113014079e-01, + 6.461720360015138498e-02, 3.514612878443492683e-01, 5.000000000000000000e-01, 3.839262482346385980e-02, + 2.088224282758644190e-01, 7.029225756886985366e-01, 1.670153200441366892e-02, 9.084178237683891710e-02, + 8.707655927996972300e-01, 3.288504389784733389e-03, 1.788659866910165264e-02, 9.745539561713791876e-01, + 1.259459028105180400e-01, 8.486080533608612031e-01, 2.544604382862070135e-02, 1.125328751958891010e-01, + 7.582327176038081706e-01, 1.292344072003027700e-01, 9.084178237683891710e-02, 6.120807933118596056e-01, + 2.970774243113014079e-01, 6.461720360015138498e-02, 4.353827963998486150e-01, 5.000000000000000000e-01, + 3.839262482346385980e-02, 2.586847994878376245e-01, 7.029225756886985366e-01, 1.670153200441366892e-02, + 1.125328751958891010e-01, 8.707655927996972300e-01, 3.288504389784733389e-03, 2.215753943883607899e-02, + 9.745539561713791876e-01, 1.259459028105180400e-01, 9.497554134892866040e-01, 2.544604382862070135e-02, + 1.125328751958891010e-01, 8.486080533608612031e-01, 1.292344072003027700e-01, 9.084178237683891710e-02, + 6.850359770195968423e-01, 2.970774243113014079e-01, 6.461720360015138498e-02, 4.872769780856895938e-01, + 5.000000000000000000e-01, 3.839262482346385980e-02, 2.895179791517822898e-01, 7.029225756886985366e-01, + 1.670153200441366892e-02, 1.259459028105180400e-01, 8.707655927996972300e-01, 3.288504389784733389e-03, + 2.479854268209272239e-02, 9.745539561713791876e-01, 2.895179791517822898e-01, 2.479854268209261484e-02, + 2.544604382862070135e-02, 2.586847994878375689e-01, 2.215753943883598184e-02, 1.292344072003027700e-01, + 2.088224282758643635e-01, 1.788659866910157631e-02, 2.970774243113014079e-01, 1.485387121556507040e-01, + 1.272302191431035068e-02, 5.000000000000000000e-01, 8.825499603543703053e-02, 7.559445159519125913e-03, + 7.029225756886985366e-01, 3.839262482346385286e-02, 3.288504389784719078e-03, 8.707655927996972300e-01, + 7.559445159519158006e-03, 6.475011465280884702e-04, 9.745539561713791876e-01, 2.895179791517822898e-01, + 1.259459028105180400e-01, 2.544604382862070135e-02, 2.586847994878375689e-01, 1.125328751958891010e-01, + 1.292344072003027700e-01, 2.088224282758643635e-01, 9.084178237683891710e-02, 2.970774243113014079e-01, + 1.485387121556507040e-01, 6.461720360015138498e-02, 5.000000000000000000e-01, 8.825499603543703053e-02, + 3.839262482346385980e-02, 7.029225756886985366e-01, 3.839262482346385286e-02, 1.670153200441366892e-02, + 8.707655927996972300e-01, 7.559445159519158006e-03, 3.288504389784733389e-03, 9.745539561713791876e-01, + 2.895179791517822898e-01, 2.895179791517822898e-01, 2.544604382862070135e-02, 2.586847994878375689e-01, + 2.586847994878375689e-01, 1.292344072003027700e-01, 2.088224282758643635e-01, 2.088224282758643635e-01, + 2.970774243113014079e-01, 1.485387121556507040e-01, 1.485387121556507040e-01, 5.000000000000000000e-01, + 8.825499603543703053e-02, 8.825499603543703053e-02, 7.029225756886985366e-01, 3.839262482346385286e-02, + 3.839262482346385286e-02, 8.707655927996972300e-01, 7.559445159519158006e-03, 7.559445159519158006e-03, + 9.745539561713791876e-01, 2.895179791517822898e-01, 4.872769780856896493e-01, 2.544604382862070135e-02, + 2.586847994878375689e-01, 4.353827963998486150e-01, 1.292344072003027700e-01, 2.088224282758643635e-01, + 3.514612878443492683e-01, 2.970774243113014079e-01, 1.485387121556507040e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 8.825499603543703053e-02, 1.485387121556507317e-01, 7.029225756886985366e-01, + 3.839262482346385286e-02, 6.461720360015138498e-02, 8.707655927996972300e-01, 7.559445159519158006e-03, + 1.272302191431040619e-02, 9.745539561713791876e-01, 2.895179791517822898e-01, 6.850359770195969533e-01, + 2.544604382862070135e-02, 2.586847994878375689e-01, 6.120807933118596056e-01, 1.292344072003027700e-01, + 2.088224282758643635e-01, 4.941001474128341453e-01, 2.970774243113014079e-01, 1.485387121556507040e-01, + 3.514612878443492683e-01, 5.000000000000000000e-01, 8.825499603543703053e-02, 2.088224282758644190e-01, + 7.029225756886985366e-01, 3.839262482346385286e-02, 9.084178237683891710e-02, 8.707655927996972300e-01, + 7.559445159519158006e-03, 1.788659866910165264e-02, 9.745539561713791876e-01, 2.895179791517822898e-01, + 8.486080533608612031e-01, 2.544604382862070135e-02, 2.586847994878375689e-01, 7.582327176038081706e-01, + 1.292344072003027700e-01, 2.088224282758643635e-01, 6.120807933118596056e-01, 2.970774243113014079e-01, + 1.485387121556507040e-01, 4.353827963998486150e-01, 5.000000000000000000e-01, 8.825499603543703053e-02, + 2.586847994878376245e-01, 7.029225756886985366e-01, 3.839262482346385286e-02, 1.125328751958891010e-01, + 8.707655927996972300e-01, 7.559445159519158006e-03, 2.215753943883607899e-02, 9.745539561713791876e-01, + 2.895179791517822898e-01, 9.497554134892866040e-01, 2.544604382862070135e-02, 2.586847994878375689e-01, + 8.486080533608612031e-01, 1.292344072003027700e-01, 2.088224282758643635e-01, 6.850359770195968423e-01, + 2.970774243113014079e-01, 1.485387121556507040e-01, 4.872769780856895938e-01, 5.000000000000000000e-01, + 8.825499603543703053e-02, 2.895179791517822898e-01, 7.029225756886985366e-01, 3.839262482346385286e-02, + 1.259459028105180400e-01, 8.707655927996972300e-01, 7.559445159519158006e-03, 2.479854268209272239e-02, + 9.745539561713791876e-01, 4.872769780856896493e-01, 2.479854268209261484e-02, 2.544604382862070135e-02, + 4.353827963998486150e-01, 2.215753943883598184e-02, 1.292344072003027700e-01, 3.514612878443492683e-01, + 1.788659866910157631e-02, 2.970774243113014079e-01, 2.500000000000000000e-01, 1.272302191431035068e-02, + 5.000000000000000000e-01, 1.485387121556507317e-01, 7.559445159519125913e-03, 7.029225756886985366e-01, + 6.461720360015138498e-02, 3.288504389784719078e-03, 8.707655927996972300e-01, 1.272302191431040619e-02, + 6.475011465280884702e-04, 9.745539561713791876e-01, 4.872769780856896493e-01, 1.259459028105180400e-01, + 2.544604382862070135e-02, 4.353827963998486150e-01, 1.125328751958891010e-01, 1.292344072003027700e-01, + 3.514612878443492683e-01, 9.084178237683891710e-02, 2.970774243113014079e-01, 2.500000000000000000e-01, + 6.461720360015138498e-02, 5.000000000000000000e-01, 1.485387121556507317e-01, 3.839262482346385980e-02, + 7.029225756886985366e-01, 6.461720360015138498e-02, 1.670153200441366892e-02, 8.707655927996972300e-01, + 1.272302191431040619e-02, 3.288504389784733389e-03, 9.745539561713791876e-01, 4.872769780856896493e-01, + 2.895179791517822898e-01, 2.544604382862070135e-02, 4.353827963998486150e-01, 2.586847994878375689e-01, + 1.292344072003027700e-01, 3.514612878443492683e-01, 2.088224282758643635e-01, 2.970774243113014079e-01, + 2.500000000000000000e-01, 1.485387121556507040e-01, 5.000000000000000000e-01, 1.485387121556507317e-01, + 8.825499603543703053e-02, 7.029225756886985366e-01, 6.461720360015138498e-02, 3.839262482346385286e-02, + 8.707655927996972300e-01, 1.272302191431040619e-02, 7.559445159519158006e-03, 9.745539561713791876e-01, + 4.872769780856896493e-01, 4.872769780856896493e-01, 2.544604382862070135e-02, 4.353827963998486150e-01, + 4.353827963998486150e-01, 1.292344072003027700e-01, 3.514612878443492683e-01, 3.514612878443492683e-01, + 2.970774243113014079e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, + 1.485387121556507317e-01, 1.485387121556507317e-01, 7.029225756886985366e-01, 6.461720360015138498e-02, + 6.461720360015138498e-02, 8.707655927996972300e-01, 1.272302191431040619e-02, 1.272302191431040619e-02, + 9.745539561713791876e-01, 4.872769780856896493e-01, 6.850359770195969533e-01, 2.544604382862070135e-02, + 4.353827963998486150e-01, 6.120807933118596056e-01, 1.292344072003027700e-01, 3.514612878443492683e-01, + 4.941001474128341453e-01, 2.970774243113014079e-01, 2.500000000000000000e-01, 3.514612878443492683e-01, + 5.000000000000000000e-01, 1.485387121556507317e-01, 2.088224282758644190e-01, 7.029225756886985366e-01, + 6.461720360015138498e-02, 9.084178237683891710e-02, 8.707655927996972300e-01, 1.272302191431040619e-02, + 1.788659866910165264e-02, 9.745539561713791876e-01, 4.872769780856896493e-01, 8.486080533608612031e-01, + 2.544604382862070135e-02, 4.353827963998486150e-01, 7.582327176038081706e-01, 1.292344072003027700e-01, + 3.514612878443492683e-01, 6.120807933118596056e-01, 2.970774243113014079e-01, 2.500000000000000000e-01, + 4.353827963998486150e-01, 5.000000000000000000e-01, 1.485387121556507317e-01, 2.586847994878376245e-01, + 7.029225756886985366e-01, 6.461720360015138498e-02, 1.125328751958891010e-01, 8.707655927996972300e-01, + 1.272302191431040619e-02, 2.215753943883607899e-02, 9.745539561713791876e-01, 4.872769780856896493e-01, + 9.497554134892866040e-01, 2.544604382862070135e-02, 4.353827963998486150e-01, 8.486080533608612031e-01, + 1.292344072003027700e-01, 3.514612878443492683e-01, 6.850359770195968423e-01, 2.970774243113014079e-01, + 2.500000000000000000e-01, 4.872769780856895938e-01, 5.000000000000000000e-01, 1.485387121556507317e-01, + 2.895179791517822898e-01, 7.029225756886985366e-01, 6.461720360015138498e-02, 1.259459028105180400e-01, + 8.707655927996972300e-01, 1.272302191431040619e-02, 2.479854268209272239e-02, 9.745539561713791876e-01, + 6.850359770195969533e-01, 2.479854268209261484e-02, 2.544604382862070135e-02, 6.120807933118596056e-01, + 2.215753943883598184e-02, 1.292344072003027700e-01, 4.941001474128341453e-01, 1.788659866910157631e-02, + 2.970774243113014079e-01, 3.514612878443492683e-01, 1.272302191431035068e-02, 5.000000000000000000e-01, + 2.088224282758644190e-01, 7.559445159519125913e-03, 7.029225756886985366e-01, 9.084178237683891710e-02, + 3.288504389784719078e-03, 8.707655927996972300e-01, 1.788659866910165264e-02, 6.475011465280884702e-04, + 9.745539561713791876e-01, 6.850359770195969533e-01, 1.259459028105180400e-01, 2.544604382862070135e-02, + 6.120807933118596056e-01, 1.125328751958891010e-01, 1.292344072003027700e-01, 4.941001474128341453e-01, + 9.084178237683891710e-02, 2.970774243113014079e-01, 3.514612878443492683e-01, 6.461720360015138498e-02, + 5.000000000000000000e-01, 2.088224282758644190e-01, 3.839262482346385980e-02, 7.029225756886985366e-01, + 9.084178237683891710e-02, 1.670153200441366892e-02, 8.707655927996972300e-01, 1.788659866910165264e-02, + 3.288504389784733389e-03, 9.745539561713791876e-01, 6.850359770195969533e-01, 2.895179791517822898e-01, + 2.544604382862070135e-02, 6.120807933118596056e-01, 2.586847994878375689e-01, 1.292344072003027700e-01, + 4.941001474128341453e-01, 2.088224282758643635e-01, 2.970774243113014079e-01, 3.514612878443492683e-01, + 1.485387121556507040e-01, 5.000000000000000000e-01, 2.088224282758644190e-01, 8.825499603543703053e-02, + 7.029225756886985366e-01, 9.084178237683891710e-02, 3.839262482346385286e-02, 8.707655927996972300e-01, + 1.788659866910165264e-02, 7.559445159519158006e-03, 9.745539561713791876e-01, 6.850359770195969533e-01, + 4.872769780856896493e-01, 2.544604382862070135e-02, 6.120807933118596056e-01, 4.353827963998486150e-01, + 1.292344072003027700e-01, 4.941001474128341453e-01, 3.514612878443492683e-01, 2.970774243113014079e-01, + 3.514612878443492683e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, 2.088224282758644190e-01, + 1.485387121556507317e-01, 7.029225756886985366e-01, 9.084178237683891710e-02, 6.461720360015138498e-02, + 8.707655927996972300e-01, 1.788659866910165264e-02, 1.272302191431040619e-02, 9.745539561713791876e-01, + 6.850359770195969533e-01, 6.850359770195969533e-01, 2.544604382862070135e-02, 6.120807933118596056e-01, + 6.120807933118596056e-01, 1.292344072003027700e-01, 4.941001474128341453e-01, 4.941001474128341453e-01, + 2.970774243113014079e-01, 3.514612878443492683e-01, 3.514612878443492683e-01, 5.000000000000000000e-01, + 2.088224282758644190e-01, 2.088224282758644190e-01, 7.029225756886985366e-01, 9.084178237683891710e-02, + 9.084178237683891710e-02, 8.707655927996972300e-01, 1.788659866910165264e-02, 1.788659866910165264e-02, + 9.745539561713791876e-01, 6.850359770195969533e-01, 8.486080533608612031e-01, 2.544604382862070135e-02, + 6.120807933118596056e-01, 7.582327176038081706e-01, 1.292344072003027700e-01, 4.941001474128341453e-01, + 6.120807933118596056e-01, 2.970774243113014079e-01, 3.514612878443492683e-01, 4.353827963998486150e-01, + 5.000000000000000000e-01, 2.088224282758644190e-01, 2.586847994878376245e-01, 7.029225756886985366e-01, + 9.084178237683891710e-02, 1.125328751958891010e-01, 8.707655927996972300e-01, 1.788659866910165264e-02, + 2.215753943883607899e-02, 9.745539561713791876e-01, 6.850359770195969533e-01, 9.497554134892866040e-01, + 2.544604382862070135e-02, 6.120807933118596056e-01, 8.486080533608612031e-01, 1.292344072003027700e-01, + 4.941001474128341453e-01, 6.850359770195968423e-01, 2.970774243113014079e-01, 3.514612878443492683e-01, + 4.872769780856895938e-01, 5.000000000000000000e-01, 2.088224282758644190e-01, 2.895179791517822898e-01, + 7.029225756886985366e-01, 9.084178237683891710e-02, 1.259459028105180400e-01, 8.707655927996972300e-01, + 1.788659866910165264e-02, 2.479854268209272239e-02, 9.745539561713791876e-01, 8.486080533608612031e-01, + 2.479854268209261484e-02, 2.544604382862070135e-02, 7.582327176038081706e-01, 2.215753943883598184e-02, + 1.292344072003027700e-01, 6.120807933118596056e-01, 1.788659866910157631e-02, 2.970774243113014079e-01, + 4.353827963998486150e-01, 1.272302191431035068e-02, 5.000000000000000000e-01, 2.586847994878376245e-01, + 7.559445159519125913e-03, 7.029225756886985366e-01, 1.125328751958891010e-01, 3.288504389784719078e-03, + 8.707655927996972300e-01, 2.215753943883607899e-02, 6.475011465280884702e-04, 9.745539561713791876e-01, + 8.486080533608612031e-01, 1.259459028105180400e-01, 2.544604382862070135e-02, 7.582327176038081706e-01, + 1.125328751958891010e-01, 1.292344072003027700e-01, 6.120807933118596056e-01, 9.084178237683891710e-02, + 2.970774243113014079e-01, 4.353827963998486150e-01, 6.461720360015138498e-02, 5.000000000000000000e-01, + 2.586847994878376245e-01, 3.839262482346385980e-02, 7.029225756886985366e-01, 1.125328751958891010e-01, + 1.670153200441366892e-02, 8.707655927996972300e-01, 2.215753943883607899e-02, 3.288504389784733389e-03, + 9.745539561713791876e-01, 8.486080533608612031e-01, 2.895179791517822898e-01, 2.544604382862070135e-02, + 7.582327176038081706e-01, 2.586847994878375689e-01, 1.292344072003027700e-01, 6.120807933118596056e-01, + 2.088224282758643635e-01, 2.970774243113014079e-01, 4.353827963998486150e-01, 1.485387121556507040e-01, + 5.000000000000000000e-01, 2.586847994878376245e-01, 8.825499603543703053e-02, 7.029225756886985366e-01, + 1.125328751958891010e-01, 3.839262482346385286e-02, 8.707655927996972300e-01, 2.215753943883607899e-02, + 7.559445159519158006e-03, 9.745539561713791876e-01, 8.486080533608612031e-01, 4.872769780856896493e-01, + 2.544604382862070135e-02, 7.582327176038081706e-01, 4.353827963998486150e-01, 1.292344072003027700e-01, + 6.120807933118596056e-01, 3.514612878443492683e-01, 2.970774243113014079e-01, 4.353827963998486150e-01, + 2.500000000000000000e-01, 5.000000000000000000e-01, 2.586847994878376245e-01, 1.485387121556507317e-01, + 7.029225756886985366e-01, 1.125328751958891010e-01, 6.461720360015138498e-02, 8.707655927996972300e-01, + 2.215753943883607899e-02, 1.272302191431040619e-02, 9.745539561713791876e-01, 8.486080533608612031e-01, + 6.850359770195969533e-01, 2.544604382862070135e-02, 7.582327176038081706e-01, 6.120807933118596056e-01, + 1.292344072003027700e-01, 6.120807933118596056e-01, 4.941001474128341453e-01, 2.970774243113014079e-01, + 4.353827963998486150e-01, 3.514612878443492683e-01, 5.000000000000000000e-01, 2.586847994878376245e-01, + 2.088224282758644190e-01, 7.029225756886985366e-01, 1.125328751958891010e-01, 9.084178237683891710e-02, + 8.707655927996972300e-01, 2.215753943883607899e-02, 1.788659866910165264e-02, 9.745539561713791876e-01, + 8.486080533608612031e-01, 8.486080533608612031e-01, 2.544604382862070135e-02, 7.582327176038081706e-01, + 7.582327176038081706e-01, 1.292344072003027700e-01, 6.120807933118596056e-01, 6.120807933118596056e-01, + 2.970774243113014079e-01, 4.353827963998486150e-01, 4.353827963998486150e-01, 5.000000000000000000e-01, + 2.586847994878376245e-01, 2.586847994878376245e-01, 7.029225756886985366e-01, 1.125328751958891010e-01, + 1.125328751958891010e-01, 8.707655927996972300e-01, 2.215753943883607899e-02, 2.215753943883607899e-02, + 9.745539561713791876e-01, 8.486080533608612031e-01, 9.497554134892866040e-01, 2.544604382862070135e-02, + 7.582327176038081706e-01, 8.486080533608612031e-01, 1.292344072003027700e-01, 6.120807933118596056e-01, + 6.850359770195968423e-01, 2.970774243113014079e-01, 4.353827963998486150e-01, 4.872769780856895938e-01, + 5.000000000000000000e-01, 2.586847994878376245e-01, 2.895179791517822898e-01, 7.029225756886985366e-01, + 1.125328751958891010e-01, 1.259459028105180400e-01, 8.707655927996972300e-01, 2.215753943883607899e-02, + 2.479854268209272239e-02, 9.745539561713791876e-01, 9.497554134892866040e-01, 2.479854268209261484e-02, + 2.544604382862070135e-02, 8.486080533608612031e-01, 2.215753943883598184e-02, 1.292344072003027700e-01, + 6.850359770195968423e-01, 1.788659866910157631e-02, 2.970774243113014079e-01, 4.872769780856895938e-01, + 1.272302191431035068e-02, 5.000000000000000000e-01, 2.895179791517822898e-01, 7.559445159519125913e-03, + 7.029225756886985366e-01, 1.259459028105180400e-01, 3.288504389784719078e-03, 8.707655927996972300e-01, + 2.479854268209272239e-02, 6.475011465280884702e-04, 9.745539561713791876e-01, 9.497554134892866040e-01, + 1.259459028105180400e-01, 2.544604382862070135e-02, 8.486080533608612031e-01, 1.125328751958891010e-01, + 1.292344072003027700e-01, 6.850359770195968423e-01, 9.084178237683891710e-02, 2.970774243113014079e-01, + 4.872769780856895938e-01, 6.461720360015138498e-02, 5.000000000000000000e-01, 2.895179791517822898e-01, + 3.839262482346385980e-02, 7.029225756886985366e-01, 1.259459028105180400e-01, 1.670153200441366892e-02, + 8.707655927996972300e-01, 2.479854268209272239e-02, 3.288504389784733389e-03, 9.745539561713791876e-01, + 9.497554134892866040e-01, 2.895179791517822898e-01, 2.544604382862070135e-02, 8.486080533608612031e-01, + 2.586847994878375689e-01, 1.292344072003027700e-01, 6.850359770195968423e-01, 2.088224282758643635e-01, + 2.970774243113014079e-01, 4.872769780856895938e-01, 1.485387121556507040e-01, 5.000000000000000000e-01, + 2.895179791517822898e-01, 8.825499603543703053e-02, 7.029225756886985366e-01, 1.259459028105180400e-01, + 3.839262482346385286e-02, 8.707655927996972300e-01, 2.479854268209272239e-02, 7.559445159519158006e-03, + 9.745539561713791876e-01, 9.497554134892866040e-01, 4.872769780856896493e-01, 2.544604382862070135e-02, + 8.486080533608612031e-01, 4.353827963998486150e-01, 1.292344072003027700e-01, 6.850359770195968423e-01, + 3.514612878443492683e-01, 2.970774243113014079e-01, 4.872769780856895938e-01, 2.500000000000000000e-01, + 5.000000000000000000e-01, 2.895179791517822898e-01, 1.485387121556507317e-01, 7.029225756886985366e-01, + 1.259459028105180400e-01, 6.461720360015138498e-02, 8.707655927996972300e-01, 2.479854268209272239e-02, + 1.272302191431040619e-02, 9.745539561713791876e-01, 9.497554134892866040e-01, 6.850359770195969533e-01, + 2.544604382862070135e-02, 8.486080533608612031e-01, 6.120807933118596056e-01, 1.292344072003027700e-01, + 6.850359770195968423e-01, 4.941001474128341453e-01, 2.970774243113014079e-01, 4.872769780856895938e-01, + 3.514612878443492683e-01, 5.000000000000000000e-01, 2.895179791517822898e-01, 2.088224282758644190e-01, + 7.029225756886985366e-01, 1.259459028105180400e-01, 9.084178237683891710e-02, 8.707655927996972300e-01, + 2.479854268209272239e-02, 1.788659866910165264e-02, 9.745539561713791876e-01, 9.497554134892866040e-01, + 8.486080533608612031e-01, 2.544604382862070135e-02, 8.486080533608612031e-01, 7.582327176038081706e-01, + 1.292344072003027700e-01, 6.850359770195968423e-01, 6.120807933118596056e-01, 2.970774243113014079e-01, + 4.872769780856895938e-01, 4.353827963998486150e-01, 5.000000000000000000e-01, 2.895179791517822898e-01, + 2.586847994878376245e-01, 7.029225756886985366e-01, 1.259459028105180400e-01, 1.125328751958891010e-01, + 8.707655927996972300e-01, 2.479854268209272239e-02, 2.215753943883607899e-02, 9.745539561713791876e-01, + 9.497554134892866040e-01, 9.497554134892866040e-01, 2.544604382862070135e-02, 8.486080533608612031e-01, + 8.486080533608612031e-01, 1.292344072003027700e-01, 6.850359770195968423e-01, 6.850359770195968423e-01, + 2.970774243113014079e-01, 4.872769780856895938e-01, 4.872769780856895938e-01, 5.000000000000000000e-01, + 2.895179791517822898e-01, 2.895179791517822898e-01, 7.029225756886985366e-01, 1.259459028105180400e-01, + 1.259459028105180400e-01, 8.707655927996972300e-01, 2.479854268209272239e-02, 2.479854268209272239e-02, + 9.745539561713791876e-01 +}; +inline constexpr double pyramid_o10_weights[343] = { + 2.577388186715318021e-04, 4.444798385185597198e-04, 3.953973886067965586e-04, 2.189891456490211641e-04, + 7.062494343835647019e-05, 9.790522191912680791e-06, 1.757149032522950112e-07, 5.567514076073230801e-04, + 9.601377744485532599e-04, 8.541129109140622490e-04, 4.730467677288396184e-04, 1.525596216905281870e-04, + 2.114887869686736161e-05, 3.795684337638560425e-07, 7.600297475626251490e-04, 1.310698563790889956e-03, + 1.165962422729679591e-03, 6.457632806127400118e-04, 2.082614416010263665e-04, 2.887065343272472059e-05, + 5.181545963863137413e-07, 8.319445063022067998e-04, 1.434718144994942025e-03, 1.276286928578200834e-03, + 7.068660343892551202e-04, 2.279673430812328646e-04, 3.160242292323061600e-05, 5.671828915397961299e-07, + 7.600297475626254743e-04, 1.310698563790890390e-03, 1.165962422729680025e-03, 6.457632806127403370e-04, + 2.082614416010264478e-04, 2.887065343272473075e-05, 5.181545963863139530e-07, 5.567514076073230801e-04, + 9.601377744485532599e-04, 8.541129109140622490e-04, 4.730467677288396184e-04, 1.525596216905281870e-04, + 2.114887869686736161e-05, 3.795684337638560425e-07, 2.577388186715329947e-04, 4.444798385185618340e-04, + 3.953973886067984017e-04, 2.189891456490221941e-04, 7.062494343835679545e-05, 9.790522191912728224e-06, + 1.757149032522958053e-07, 5.567514076073230801e-04, 9.601377744485532599e-04, 8.541129109140622490e-04, + 4.730467677288396184e-04, 1.525596216905281870e-04, 2.114887869686736161e-05, 3.795684337638560425e-07, + 1.202659853375719285e-03, 2.074030059486100652e-03, 1.845001726391151849e-03, 1.021846282816907779e-03, + 3.295498309414108379e-04, 4.568449581823890153e-05, 8.199201845906319763e-07, 1.641769113244050593e-03, + 2.831289730048243934e-03, 2.518639696643006336e-03, 1.394937779708141107e-03, 4.498734469233012039e-04, + 6.236459459254939223e-05, 1.119285416078259340e-06, 1.797114913936298432e-03, 3.099189135974454810e-03, + 2.756955850342096385e-03, 1.526928157987608118e-03, 4.924409128712935527e-04, 6.826559358423091935e-05, + 1.225193297863330996e-06, 1.641769113244051677e-03, 2.831289730048245669e-03, 2.518639696643007637e-03, + 1.394937779708141758e-03, 4.498734469233015292e-04, 6.236459459254941934e-05, 1.119285416078259975e-06, + 1.202659853375719285e-03, 2.074030059486100652e-03, 1.845001726391151849e-03, 1.021846282816907779e-03, + 3.295498309414108379e-04, 4.568449581823890153e-05, 8.199201845906319763e-07, 5.567514076073255738e-04, + 9.601377744485577051e-04, 8.541129109140661521e-04, 4.730467677288417868e-04, 1.525596216905289188e-04, + 2.114887869686745987e-05, 3.795684337638577895e-07, 7.600297475626251490e-04, 1.310698563790889956e-03, + 1.165962422729679591e-03, 6.457632806127400118e-04, 2.082614416010263665e-04, 2.887065343272472059e-05, + 5.181545963863137413e-07, 1.641769113244050593e-03, 2.831289730048243934e-03, 2.518639696643006336e-03, + 1.394937779708141107e-03, 4.498734469233012039e-04, 6.236459459254939223e-05, 1.119285416078259340e-06, + 2.241203789780195441e-03, 3.865036332917421141e-03, 3.438233054618347346e-03, 1.904250611836625327e-03, + 6.141290307098765241e-04, 8.513484912183872093e-05, 1.527953410820075491e-06, 2.453268686378204909e-03, + 4.230749854385304487e-03, 3.763562032077824582e-03, 2.084432668879922582e-03, 6.722385207924800200e-04, + 9.319039188784505813e-05, 1.672529858329970760e-06, 2.241203789780196309e-03, 3.865036332917422442e-03, + 3.438233054618348647e-03, 1.904250611836625978e-03, 6.141290307098768494e-04, 8.513484912183874804e-05, + 1.527953410820076126e-06, 1.641769113244050593e-03, 2.831289730048243934e-03, 2.518639696643006336e-03, + 1.394937779708141107e-03, 4.498734469233012039e-04, 6.236459459254939223e-05, 1.119285416078259340e-06, + 7.600297475626287269e-04, 1.310698563790896028e-03, 1.165962422729685012e-03, 6.457632806127430476e-04, + 2.082614416010273423e-04, 2.887065343272485611e-05, 5.181545963863161765e-07, 8.319445063022067998e-04, + 1.434718144994942025e-03, 1.276286928578200834e-03, 7.068660343892551202e-04, 2.279673430812328646e-04, + 3.160242292323061600e-05, 5.671828915397961299e-07, 1.797114913936298432e-03, 3.099189135974454810e-03, + 2.756955850342096385e-03, 1.526928157987608118e-03, 4.924409128712935527e-04, 6.826559358423091935e-05, + 1.225193297863330996e-06, 2.453268686378204909e-03, 4.230749854385304487e-03, 3.763562032077824582e-03, + 2.084432668879922582e-03, 6.722385207924800200e-04, 9.319039188784505813e-05, 1.672529858329970760e-06, + 2.685399371091597327e-03, 4.631067547266884583e-03, 4.119673955862789005e-03, 2.281663728548479538e-03, + 7.358463877125326130e-04, 1.020081579962817917e-04, 1.830786270835240204e-06, 2.453268686378205776e-03, + 4.230749854385306222e-03, 3.763562032077826750e-03, 2.084432668879923883e-03, 6.722385207924803452e-04, + 9.319039188784509879e-05, 1.672529858329971607e-06, 1.797114913936298432e-03, 3.099189135974454810e-03, + 2.756955850342096385e-03, 1.526928157987608118e-03, 4.924409128712935527e-04, 6.826559358423091935e-05, + 1.225193297863330996e-06, 8.319445063022108114e-04, 1.434718144994948747e-03, 1.276286928578206905e-03, + 7.068660343892584812e-04, 2.279673430812339488e-04, 3.160242292323076508e-05, 5.671828915397988828e-07, + 7.600297475626254743e-04, 1.310698563790890390e-03, 1.165962422729680025e-03, 6.457632806127403370e-04, + 2.082614416010264478e-04, 2.887065343272473075e-05, 5.181545963863139530e-07, 1.641769113244051677e-03, + 2.831289730048245669e-03, 2.518639696643007637e-03, 1.394937779708141758e-03, 4.498734469233015292e-04, + 6.236459459254941934e-05, 1.119285416078259975e-06, 2.241203789780196309e-03, 3.865036332917422442e-03, + 3.438233054618348647e-03, 1.904250611836625978e-03, 6.141290307098768494e-04, 8.513484912183874804e-05, + 1.527953410820076126e-06, 2.453268686378205776e-03, 4.230749854385306222e-03, 3.763562032077826750e-03, + 2.084432668879923883e-03, 6.722385207924803452e-04, 9.319039188784509879e-05, 1.672529858329971607e-06, + 2.241203789780197610e-03, 3.865036332917425044e-03, 3.438233054618350815e-03, 1.904250611836627062e-03, + 6.141290307098771746e-04, 8.513484912183880225e-05, 1.527953410820076973e-06, 1.641769113244051677e-03, + 2.831289730048245669e-03, 2.518639696643007637e-03, 1.394937779708141758e-03, 4.498734469233015292e-04, + 6.236459459254941934e-05, 1.119285416078259975e-06, 7.600297475626290522e-04, 1.310698563790896462e-03, + 1.165962422729685446e-03, 6.457632806127432644e-04, 2.082614416010273965e-04, 2.887065343272486628e-05, + 5.181545963863163882e-07, 5.567514076073230801e-04, 9.601377744485532599e-04, 8.541129109140622490e-04, + 4.730467677288396184e-04, 1.525596216905281870e-04, 2.114887869686736161e-05, 3.795684337638560425e-07, + 1.202659853375719285e-03, 2.074030059486100652e-03, 1.845001726391151849e-03, 1.021846282816907779e-03, + 3.295498309414108379e-04, 4.568449581823890153e-05, 8.199201845906319763e-07, 1.641769113244050593e-03, + 2.831289730048243934e-03, 2.518639696643006336e-03, 1.394937779708141107e-03, 4.498734469233012039e-04, + 6.236459459254939223e-05, 1.119285416078259340e-06, 1.797114913936298432e-03, 3.099189135974454810e-03, + 2.756955850342096385e-03, 1.526928157987608118e-03, 4.924409128712935527e-04, 6.826559358423091935e-05, + 1.225193297863330996e-06, 1.641769113244051677e-03, 2.831289730048245669e-03, 2.518639696643007637e-03, + 1.394937779708141758e-03, 4.498734469233015292e-04, 6.236459459254941934e-05, 1.119285416078259975e-06, + 1.202659853375719285e-03, 2.074030059486100652e-03, 1.845001726391151849e-03, 1.021846282816907779e-03, + 3.295498309414108379e-04, 4.568449581823890153e-05, 8.199201845906319763e-07, 5.567514076073255738e-04, + 9.601377744485577051e-04, 8.541129109140661521e-04, 4.730467677288417868e-04, 1.525596216905289188e-04, + 2.114887869686745987e-05, 3.795684337638577895e-07, 2.577388186715329947e-04, 4.444798385185618340e-04, + 3.953973886067984017e-04, 2.189891456490221941e-04, 7.062494343835679545e-05, 9.790522191912728224e-06, + 1.757149032522958053e-07, 5.567514076073255738e-04, 9.601377744485577051e-04, 8.541129109140661521e-04, + 4.730467677288417868e-04, 1.525596216905289188e-04, 2.114887869686745987e-05, 3.795684337638577895e-07, + 7.600297475626287269e-04, 1.310698563790896028e-03, 1.165962422729685012e-03, 6.457632806127430476e-04, + 2.082614416010273423e-04, 2.887065343272485611e-05, 5.181545963863161765e-07, 8.319445063022108114e-04, + 1.434718144994948747e-03, 1.276286928578206905e-03, 7.068660343892584812e-04, 2.279673430812339488e-04, + 3.160242292323076508e-05, 5.671828915397988828e-07, 7.600297475626290522e-04, 1.310698563790896462e-03, + 1.165962422729685446e-03, 6.457632806127432644e-04, 2.082614416010273965e-04, 2.887065343272486628e-05, + 5.181545963863163882e-07, 5.567514076073255738e-04, 9.601377744485577051e-04, 8.541129109140661521e-04, + 4.730467677288417868e-04, 1.525596216905289188e-04, 2.114887869686745987e-05, 3.795684337638577895e-07, + 2.577388186715341874e-04, 4.444798385185638398e-04, 3.953973886068001906e-04, 2.189891456490231970e-04, + 7.062494343835712071e-05, 9.790522191912770576e-06, 1.757149032522966258e-07 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_quadrilateral.h b/cpp/src/generated/quadrature_tables_quadrilateral.h new file mode 100644 index 0000000..d289d99 --- /dev/null +++ b/cpp/src/generated/quadrature_tables_quadrilateral.h @@ -0,0 +1,207 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'quadrilateral', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 1 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o1_npts = 1; +inline constexpr int quadrilateral_o1_tdim = 2; +inline constexpr double quadrilateral_o1_points[2] = { + 5.000000000000000000e-01, 5.000000000000000000e-01 +}; +inline constexpr double quadrilateral_o1_weights[1] = { + 1.000000000000000000e+00 +}; + +// ---- order 2: 4 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o2_npts = 4; +inline constexpr int quadrilateral_o2_tdim = 2; +inline constexpr double quadrilateral_o2_points[8] = { + 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 7.886751345948128655e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01 +}; +inline constexpr double quadrilateral_o2_weights[4] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01 +}; + +// ---- order 3: 4 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o3_npts = 4; +inline constexpr int quadrilateral_o3_tdim = 2; +inline constexpr double quadrilateral_o3_points[8] = { + 2.113248654051871345e-01, 2.113248654051871345e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, + 7.886751345948128655e-01, 2.113248654051871345e-01, 7.886751345948128655e-01, 7.886751345948128655e-01 +}; +inline constexpr double quadrilateral_o3_weights[4] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01 +}; + +// ---- order 4: 9 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o4_npts = 9; +inline constexpr int quadrilateral_o4_tdim = 2; +inline constexpr double quadrilateral_o4_points[18] = { + 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01 +}; +inline constexpr double quadrilateral_o4_weights[9] = { + 7.716049382716043403e-02, 1.234567901234567416e-01, 7.716049382716043403e-02, 1.234567901234567416e-01, + 1.975308641975308532e-01, 1.234567901234567416e-01, 7.716049382716043403e-02, 1.234567901234567416e-01, + 7.716049382716043403e-02 +}; + +// ---- order 5: 9 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o5_npts = 9; +inline constexpr int quadrilateral_o5_tdim = 2; +inline constexpr double quadrilateral_o5_points[18] = { + 1.127016653792582979e-01, 1.127016653792582979e-01, 1.127016653792582979e-01, 5.000000000000000000e-01, + 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, 1.127016653792582979e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 8.872983346207417021e-01, + 8.872983346207417021e-01, 1.127016653792582979e-01, 8.872983346207417021e-01, 5.000000000000000000e-01, + 8.872983346207417021e-01, 8.872983346207417021e-01 +}; +inline constexpr double quadrilateral_o5_weights[9] = { + 7.716049382716043403e-02, 1.234567901234567416e-01, 7.716049382716043403e-02, 1.234567901234567416e-01, + 1.975308641975308532e-01, 1.234567901234567416e-01, 7.716049382716043403e-02, 1.234567901234567416e-01, + 7.716049382716043403e-02 +}; + +// ---- order 6: 16 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o6_npts = 16; +inline constexpr int quadrilateral_o6_tdim = 2; +inline constexpr double quadrilateral_o6_points[32] = { + 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.943184420297371373e-02, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, 3.300094782075718713e-01, + 3.300094782075718713e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, 3.300094782075718713e-01, + 9.305681557970262308e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01 +}; +inline constexpr double quadrilateral_o6_weights[16] = { + 3.025074832140046965e-02, 5.671296296296293726e-02, 5.671296296296292339e-02, 3.025074832140046965e-02, + 5.671296296296293726e-02, 1.063233257526735864e-01, 1.063233257526735587e-01, 5.671296296296293726e-02, + 5.671296296296292339e-02, 1.063233257526735587e-01, 1.063233257526735170e-01, 5.671296296296292339e-02, + 3.025074832140046965e-02, 5.671296296296293726e-02, 5.671296296296292339e-02, 3.025074832140046965e-02 +}; + +// ---- order 7: 16 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o7_npts = 16; +inline constexpr int quadrilateral_o7_tdim = 2; +inline constexpr double quadrilateral_o7_points[32] = { + 6.943184420297371373e-02, 6.943184420297371373e-02, 6.943184420297371373e-02, 3.300094782075718713e-01, + 6.943184420297371373e-02, 6.699905217924281287e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, + 3.300094782075718713e-01, 6.943184420297371373e-02, 3.300094782075718713e-01, 3.300094782075718713e-01, + 3.300094782075718713e-01, 6.699905217924281287e-01, 3.300094782075718713e-01, 9.305681557970262308e-01, + 6.699905217924281287e-01, 6.943184420297371373e-02, 6.699905217924281287e-01, 3.300094782075718713e-01, + 6.699905217924281287e-01, 6.699905217924281287e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, + 9.305681557970262308e-01, 6.943184420297371373e-02, 9.305681557970262308e-01, 3.300094782075718713e-01, + 9.305681557970262308e-01, 6.699905217924281287e-01, 9.305681557970262308e-01, 9.305681557970262308e-01 +}; +inline constexpr double quadrilateral_o7_weights[16] = { + 3.025074832140046965e-02, 5.671296296296293726e-02, 5.671296296296292339e-02, 3.025074832140046965e-02, + 5.671296296296293726e-02, 1.063233257526735864e-01, 1.063233257526735587e-01, 5.671296296296293726e-02, + 5.671296296296292339e-02, 1.063233257526735587e-01, 1.063233257526735170e-01, 5.671296296296292339e-02, + 3.025074832140046965e-02, 5.671296296296293726e-02, 5.671296296296292339e-02, 3.025074832140046965e-02 +}; + +// ---- order 8: 25 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o8_npts = 25; +inline constexpr int quadrilateral_o8_tdim = 2; +inline constexpr double quadrilateral_o8_points[50] = { + 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01 +}; +inline constexpr double quadrilateral_o8_weights[25] = { + 1.403358721560716248e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02, 2.835000000000001408e-02, 5.727135105599782255e-02, 6.807163313768768709e-02, + 5.727135105599782255e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 6.807163313768768709e-02, + 8.090864197530861501e-02, 6.807163313768768709e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 5.727135105599782255e-02, 6.807163313768768709e-02, 5.727135105599782255e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02 +}; + +// ---- order 9: 25 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o9_npts = 25; +inline constexpr int quadrilateral_o9_tdim = 2; +inline constexpr double quadrilateral_o9_points[50] = { + 4.691007703066801815e-02, 4.691007703066801815e-02, 4.691007703066801815e-02, 2.307653449471584461e-01, + 4.691007703066801815e-02, 5.000000000000000000e-01, 4.691007703066801815e-02, 7.692346550528414983e-01, + 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, 4.691007703066801815e-02, + 2.307653449471584461e-01, 2.307653449471584461e-01, 2.307653449471584461e-01, 5.000000000000000000e-01, + 2.307653449471584461e-01, 7.692346550528414983e-01, 2.307653449471584461e-01, 9.530899229693319263e-01, + 5.000000000000000000e-01, 4.691007703066801815e-02, 5.000000000000000000e-01, 2.307653449471584461e-01, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 7.692346550528414983e-01, + 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, 4.691007703066801815e-02, + 7.692346550528414983e-01, 2.307653449471584461e-01, 7.692346550528414983e-01, 5.000000000000000000e-01, + 7.692346550528414983e-01, 7.692346550528414983e-01, 7.692346550528414983e-01, 9.530899229693319263e-01, + 9.530899229693319263e-01, 4.691007703066801815e-02, 9.530899229693319263e-01, 2.307653449471584461e-01, + 9.530899229693319263e-01, 5.000000000000000000e-01, 9.530899229693319263e-01, 7.692346550528414983e-01, + 9.530899229693319263e-01, 9.530899229693319263e-01 +}; +inline constexpr double quadrilateral_o9_weights[25] = { + 1.403358721560716248e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02, 2.835000000000001408e-02, 5.727135105599782255e-02, 6.807163313768768709e-02, + 5.727135105599782255e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 6.807163313768768709e-02, + 8.090864197530861501e-02, 6.807163313768768709e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 5.727135105599782255e-02, 6.807163313768768709e-02, 5.727135105599782255e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02, 2.835000000000001408e-02, 3.369626809688022057e-02, 2.835000000000001408e-02, + 1.403358721560716248e-02 +}; + +// ---- order 10: 36 point(s), tdim=2 ---- +inline constexpr int quadrilateral_o10_npts = 36; +inline constexpr int quadrilateral_o10_tdim = 2; +inline constexpr double quadrilateral_o10_points[72] = { + 3.376524289842397497e-02, 3.376524289842397497e-02, 3.376524289842397497e-02, 1.693953067668677037e-01, + 3.376524289842397497e-02, 3.806904069584015060e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, + 3.376524289842397497e-02, 8.306046932331323518e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, + 1.693953067668677037e-01, 3.376524289842397497e-02, 1.693953067668677037e-01, 1.693953067668677037e-01, + 1.693953067668677037e-01, 3.806904069584015060e-01, 1.693953067668677037e-01, 6.193095930415984940e-01, + 1.693953067668677037e-01, 8.306046932331323518e-01, 1.693953067668677037e-01, 9.662347571015760250e-01, + 3.806904069584015060e-01, 3.376524289842397497e-02, 3.806904069584015060e-01, 1.693953067668677037e-01, + 3.806904069584015060e-01, 3.806904069584015060e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, + 3.806904069584015060e-01, 8.306046932331323518e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, + 6.193095930415984940e-01, 3.376524289842397497e-02, 6.193095930415984940e-01, 1.693953067668677037e-01, + 6.193095930415984940e-01, 3.806904069584015060e-01, 6.193095930415984940e-01, 6.193095930415984940e-01, + 6.193095930415984940e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, 9.662347571015760250e-01, + 8.306046932331323518e-01, 3.376524289842397497e-02, 8.306046932331323518e-01, 1.693953067668677037e-01, + 8.306046932331323518e-01, 3.806904069584015060e-01, 8.306046932331323518e-01, 6.193095930415984940e-01, + 8.306046932331323518e-01, 8.306046932331323518e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, + 9.662347571015760250e-01, 3.376524289842397497e-02, 9.662347571015760250e-01, 1.693953067668677037e-01, + 9.662347571015760250e-01, 3.806904069584015060e-01, 9.662347571015760250e-01, 6.193095930415984940e-01, + 9.662347571015760250e-01, 8.306046932331323518e-01, 9.662347571015760250e-01, 9.662347571015760250e-01 +}; +inline constexpr double quadrilateral_o10_weights[36] = { + 7.338020422245066822e-03, 1.545182334309579825e-02, 2.004127932945162088e-02, 2.004127932945160701e-02, + 1.545182334309579825e-02, 7.338020422245066822e-03, 1.545182334309579825e-02, 3.253722814704185923e-02, + 4.220134177189697328e-02, 4.220134177189695246e-02, 3.253722814704185923e-02, 1.545182334309579825e-02, + 2.004127932945162088e-02, 4.220134177189697328e-02, 5.473586254182419980e-02, 5.473586254182416511e-02, + 4.220134177189697328e-02, 2.004127932945162088e-02, 2.004127932945160701e-02, 4.220134177189695246e-02, + 5.473586254182416511e-02, 5.473586254182413735e-02, 4.220134177189695246e-02, 2.004127932945160701e-02, + 1.545182334309579825e-02, 3.253722814704185923e-02, 4.220134177189697328e-02, 4.220134177189695246e-02, + 3.253722814704185923e-02, 1.545182334309579825e-02, 7.338020422245066822e-03, 1.545182334309579825e-02, + 2.004127932945162088e-02, 2.004127932945160701e-02, 1.545182334309579825e-02, 7.338020422245066822e-03 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_tetrahedron.h b/cpp/src/generated/quadrature_tables_tetrahedron.h new file mode 100644 index 0000000..8f20710 --- /dev/null +++ b/cpp/src/generated/quadrature_tables_tetrahedron.h @@ -0,0 +1,370 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'tetrahedron', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 1 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o1_npts = 1; +inline constexpr int tetrahedron_o1_tdim = 3; +inline constexpr double tetrahedron_o1_points[3] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01 +}; +inline constexpr double tetrahedron_o1_weights[1] = { + 1.666666666666666574e-01 +}; + +// ---- order 2: 4 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o2_npts = 4; +inline constexpr int tetrahedron_o2_tdim = 3; +inline constexpr double tetrahedron_o2_points[12] = { + 5.854101966249689593e-01, 1.381966011250110038e-01, 1.381966011250110038e-01, 1.381966011250110038e-01, + 5.854101966249689593e-01, 1.381966011250110038e-01, 1.381966011250110038e-01, 1.381966011250110038e-01, + 5.854101966249689593e-01, 1.381966011250110038e-01, 1.381966011250110038e-01, 1.381966011250110038e-01 +}; +inline constexpr double tetrahedron_o2_weights[4] = { + 4.166666666666666435e-02, 4.166666666666666435e-02, 4.166666666666666435e-02, 4.166666666666666435e-02 +}; + +// ---- order 3: 5 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o3_npts = 5; +inline constexpr int tetrahedron_o3_tdim = 3; +inline constexpr double tetrahedron_o3_points[15] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 5.000000000000000000e-01, + 1.666666666666666019e-01, 1.666666666666666019e-01, 1.666666666666666019e-01, 5.000000000000000000e-01, + 1.666666666666666019e-01, 1.666666666666666019e-01, 1.666666666666666019e-01, 5.000000000000000000e-01, + 1.666666666666666019e-01, 1.666666666666666019e-01, 1.666666666666666019e-01 +}; +inline constexpr double tetrahedron_o3_weights[5] = { + -1.333333333333333315e-01, 7.499999999999999722e-02, 7.499999999999999722e-02, 7.499999999999999722e-02, + 7.499999999999999722e-02 +}; + +// ---- order 4: 14 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o4_npts = 14; +inline constexpr int tetrahedron_o4_tdim = 3; +inline constexpr double tetrahedron_o4_points[42] = { + 0.000000000000000000e+00, 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 0.000000000000000000e+00, 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, + 0.000000000000000000e+00, 5.000000000000000000e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 5.000000000000000000e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 5.000000000000000000e-01, 6.984197043243866032e-01, 1.005267652252044980e-01, + 1.005267652252044980e-01, 1.005267652252044980e-01, 1.005267652252044980e-01, 1.005267652252044980e-01, + 1.005267652252044980e-01, 1.005267652252044980e-01, 6.984197043243866032e-01, 1.005267652252044980e-01, + 6.984197043243866032e-01, 1.005267652252044980e-01, 5.688137952042340156e-02, 3.143728734931922064e-01, + 3.143728734931922064e-01, 3.143728734931922064e-01, 3.143728734931922064e-01, 3.143728734931922064e-01, + 3.143728734931922064e-01, 3.143728734931922064e-01, 5.688137952042340156e-02, 3.143728734931922064e-01, + 5.688137952042340156e-02, 3.143728734931922064e-01 +}; +inline constexpr double tetrahedron_o4_weights[14] = { + 3.174603174603166794e-03, 3.174603174603166794e-03, 3.174603174603166794e-03, 3.174603174603166794e-03, + 3.174603174603166794e-03, 3.174603174603166794e-03, 1.476497079049678314e-02, 1.476497079049678314e-02, + 1.476497079049678314e-02, 1.476497079049678314e-02, 2.213979111426511714e-02, 2.213979111426511714e-02, + 2.213979111426511714e-02, 2.213979111426511714e-02 +}; + +// ---- order 5: 15 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o5_npts = 15; +inline constexpr int tetrahedron_o5_tdim = 3; +inline constexpr double tetrahedron_o5_points[45] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 0.000000000000000000e+00, + 3.333333333333333148e-01, 3.333333333333333148e-01, 3.333333333333333148e-01, 3.333333333333333148e-01, + 3.333333333333333148e-01, 3.333333333333333148e-01, 3.333333333333333148e-01, 0.000000000000000000e+00, + 3.333333333333333148e-01, 0.000000000000000000e+00, 3.333333333333333148e-01, 7.272727272727272929e-01, + 9.090909090909089774e-02, 9.090909090909089774e-02, 9.090909090909089774e-02, 9.090909090909089774e-02, + 9.090909090909089774e-02, 9.090909090909089774e-02, 9.090909090909089774e-02, 7.272727272727272929e-01, + 9.090909090909089774e-02, 7.272727272727272929e-01, 9.090909090909089774e-02, 4.334498464263357165e-01, + 6.655015357366429740e-02, 6.655015357366429740e-02, 6.655015357366429740e-02, 4.334498464263357165e-01, + 6.655015357366429740e-02, 6.655015357366429740e-02, 6.655015357366429740e-02, 4.334498464263357165e-01, + 6.655015357366429740e-02, 4.334498464263357165e-01, 4.334498464263357165e-01, 4.334498464263357165e-01, + 6.655015357366429740e-02, 4.334498464263357165e-01, 4.334498464263357165e-01, 4.334498464263357165e-01, + 6.655015357366429740e-02 +}; +inline constexpr double tetrahedron_o5_weights[15] = { + 3.028367809708918182e-02, 6.026785714285717160e-03, 6.026785714285717160e-03, 6.026785714285717160e-03, + 6.026785714285717160e-03, 1.164524908602896681e-02, 1.164524908602896681e-02, 1.164524908602896681e-02, + 1.164524908602896681e-02, 1.094914156138644852e-02, 1.094914156138644852e-02, 1.094914156138644852e-02, + 1.094914156138644852e-02, 1.094914156138644852e-02, 1.094914156138644852e-02 +}; + +// ---- order 6: 24 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o6_npts = 24; +inline constexpr int tetrahedron_o6_tdim = 3; +inline constexpr double tetrahedron_o6_points[72] = { + 3.561913862225449257e-01, 2.146028712591517007e-01, 2.146028712591517007e-01, 2.146028712591517007e-01, + 2.146028712591517007e-01, 2.146028712591517007e-01, 2.146028712591517007e-01, 2.146028712591517007e-01, + 3.561913862225449257e-01, 2.146028712591517007e-01, 3.561913862225449257e-01, 2.146028712591517007e-01, + 8.779781243961659598e-01, 4.067395853461130278e-02, 4.067395853461130278e-02, 4.067395853461130278e-02, + 4.067395853461130278e-02, 4.067395853461130278e-02, 4.067395853461130278e-02, 4.067395853461130278e-02, + 8.779781243961659598e-01, 4.067395853461130278e-02, 8.779781243961659598e-01, 4.067395853461130278e-02, + 3.298632957317310099e-02, 3.223378901422757070e-01, 3.223378901422757070e-01, 3.223378901422757070e-01, + 3.223378901422757070e-01, 3.223378901422757070e-01, 3.223378901422757070e-01, 3.223378901422757070e-01, + 3.298632957317310099e-02, 3.223378901422757070e-01, 3.298632957317310099e-02, 3.223378901422757070e-01, + 2.696723314583158726e-01, 6.366100187501749774e-02, 6.366100187501749774e-02, 6.366100187501749774e-02, + 2.696723314583158726e-01, 6.366100187501749774e-02, 6.366100187501749774e-02, 6.366100187501749774e-02, + 2.696723314583158726e-01, 6.030056647916490764e-01, 6.366100187501749774e-02, 6.366100187501749774e-02, + 6.366100187501749774e-02, 6.030056647916490764e-01, 6.366100187501749774e-02, 6.366100187501749774e-02, + 6.366100187501749774e-02, 6.030056647916490764e-01, 6.366100187501749774e-02, 2.696723314583158726e-01, + 6.030056647916490764e-01, 2.696723314583158726e-01, 6.030056647916490764e-01, 6.366100187501749774e-02, + 6.030056647916490764e-01, 6.366100187501749774e-02, 2.696723314583158726e-01, 6.366100187501749774e-02, + 6.030056647916490764e-01, 2.696723314583158726e-01, 2.696723314583158726e-01, 6.366100187501749774e-02, + 6.030056647916490764e-01, 6.030056647916490764e-01, 2.696723314583158726e-01, 6.366100187501749774e-02 +}; +inline constexpr double tetrahedron_o6_weights[24] = { + 6.653791709694649448e-03, 6.653791709694649448e-03, 6.653791709694649448e-03, 6.653791709694649448e-03, + 1.679535175886783449e-03, 1.679535175886783449e-03, 1.679535175886783449e-03, 1.679535175886783449e-03, + 9.226196923942399028e-03, 9.226196923942399028e-03, 9.226196923942399028e-03, 9.226196923942399028e-03, + 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03, + 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03, + 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03, 8.035714285714283187e-03 +}; + +// ---- order 7: 31 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o7_npts = 31; +inline constexpr int tetrahedron_o7_tdim = 3; +inline constexpr double tetrahedron_o7_points[93] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 7.653604230090440552e-01, + 7.821319233031860663e-02, 7.821319233031860663e-02, 7.821319233031860663e-02, 7.821319233031860663e-02, + 7.821319233031860663e-02, 7.821319233031860663e-02, 7.821319233031860663e-02, 7.653604230090440552e-01, + 7.821319233031860663e-02, 7.653604230090440552e-01, 7.821319233031860663e-02, 6.344703500082867764e-01, + 1.218432166639043940e-01, 1.218432166639043940e-01, 1.218432166639043940e-01, 1.218432166639043940e-01, + 1.218432166639043940e-01, 1.218432166639043940e-01, 1.218432166639043940e-01, 6.344703500082867764e-01, + 1.218432166639043940e-01, 6.344703500082867764e-01, 1.218432166639043940e-01, 2.382506660738299958e-03, + 3.325391644464206209e-01, 3.325391644464206209e-01, 3.325391644464206209e-01, 3.325391644464206209e-01, + 3.325391644464206209e-01, 3.325391644464206209e-01, 3.325391644464206209e-01, 2.382506660738299958e-03, + 3.325391644464206209e-01, 2.382506660738299958e-03, 3.325391644464206209e-01, 0.000000000000000000e+00, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 0.000000000000000000e+00, + 5.000000000000000000e-01, 5.000000000000000000e-01, 5.000000000000000000e-01, 0.000000000000000000e+00, + 5.000000000000000000e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 5.000000000000000000e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 5.000000000000000000e-01, 2.000000000000000111e-01, 1.000000000000000056e-01, 1.000000000000000056e-01, + 1.000000000000000056e-01, 2.000000000000000111e-01, 1.000000000000000056e-01, 1.000000000000000056e-01, + 1.000000000000000056e-01, 2.000000000000000111e-01, 5.999999999999999778e-01, 1.000000000000000056e-01, + 1.000000000000000056e-01, 1.000000000000000056e-01, 5.999999999999999778e-01, 1.000000000000000056e-01, + 1.000000000000000056e-01, 1.000000000000000056e-01, 5.999999999999999778e-01, 1.000000000000000056e-01, + 2.000000000000000111e-01, 5.999999999999999778e-01, 2.000000000000000111e-01, 5.999999999999999778e-01, + 1.000000000000000056e-01, 5.999999999999999778e-01, 1.000000000000000056e-01, 2.000000000000000111e-01, + 1.000000000000000056e-01, 5.999999999999999778e-01, 2.000000000000000111e-01, 2.000000000000000111e-01, + 1.000000000000000056e-01, 5.999999999999999778e-01, 5.999999999999999778e-01, 2.000000000000000111e-01, + 1.000000000000000056e-01 +}; +inline constexpr double tetrahedron_o7_weights[31] = { + 1.826422346610880043e-02, 1.059994152441416648e-02, 1.059994152441416648e-02, 1.059994152441416648e-02, + 1.059994152441416648e-02, -6.251774011432995048e-02, -6.251774011432995048e-02, -6.251774011432995048e-02, + -6.251774011432995048e-02, 4.891425263073533904e-03, 4.891425263073533904e-03, 4.891425263073533904e-03, + 4.891425263073533904e-03, 9.700176366843000011e-04, 9.700176366843000011e-04, 9.700176366843000011e-04, + 9.700176366843000011e-04, 9.700176366843000011e-04, 9.700176366843000011e-04, 2.755731922398508074e-02, + 2.755731922398508074e-02, 2.755731922398508074e-02, 2.755731922398508074e-02, 2.755731922398508074e-02, + 2.755731922398508074e-02, 2.755731922398508074e-02, 2.755731922398508074e-02, 2.755731922398508074e-02, + 2.755731922398508074e-02, 2.755731922398508074e-02, 2.755731922398508074e-02 +}; + +// ---- order 8: 45 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o8_npts = 45; +inline constexpr int tetrahedron_o8_tdim = 3; +inline constexpr double tetrahedron_o8_points[135] = { + 2.500000000000000000e-01, 2.500000000000000000e-01, 2.500000000000000000e-01, 6.175871903000830221e-01, + 1.274709365666389926e-01, 1.274709365666389926e-01, 1.274709365666389926e-01, 1.274709365666389926e-01, + 1.274709365666389926e-01, 1.274709365666389926e-01, 1.274709365666389926e-01, 6.175871903000830221e-01, + 1.274709365666389926e-01, 6.175871903000830221e-01, 1.274709365666389926e-01, 9.037635088221031010e-01, + 3.207883039263229735e-02, 3.207883039263229735e-02, 3.207883039263229735e-02, 3.207883039263229735e-02, + 3.207883039263229735e-02, 3.207883039263229735e-02, 3.207883039263229735e-02, 9.037635088221031010e-01, + 3.207883039263229735e-02, 9.037635088221031010e-01, 3.207883039263229735e-02, 4.502229043567190225e-01, + 4.977709564328099828e-02, 4.977709564328099828e-02, 4.977709564328099828e-02, 4.502229043567190225e-01, + 4.977709564328099828e-02, 4.977709564328099828e-02, 4.977709564328099828e-02, 4.502229043567190225e-01, + 4.977709564328099828e-02, 4.502229043567190225e-01, 4.502229043567190225e-01, 4.502229043567190225e-01, + 4.977709564328099828e-02, 4.502229043567190225e-01, 4.502229043567190225e-01, 4.502229043567190225e-01, + 4.977709564328099828e-02, 3.162695526014501213e-01, 1.837304473985499065e-01, 1.837304473985499065e-01, + 1.837304473985499065e-01, 3.162695526014501213e-01, 1.837304473985499065e-01, 1.837304473985499065e-01, + 1.837304473985499065e-01, 3.162695526014501213e-01, 1.837304473985499065e-01, 3.162695526014501213e-01, + 3.162695526014501213e-01, 3.162695526014501213e-01, 1.837304473985499065e-01, 3.162695526014501213e-01, + 3.162695526014501213e-01, 3.162695526014501213e-01, 1.837304473985499065e-01, 2.291778784481710018e-02, + 2.319010893971509002e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, 2.291778784481710018e-02, + 2.319010893971509002e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, 2.291778784481710018e-02, + 5.132800333608811272e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, + 5.132800333608811272e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, 2.319010893971509002e-01, + 5.132800333608811272e-01, 2.319010893971509002e-01, 2.291778784481710018e-02, 5.132800333608811272e-01, + 2.291778784481710018e-02, 5.132800333608811272e-01, 2.319010893971509002e-01, 5.132800333608811272e-01, + 2.319010893971509002e-01, 2.291778784481710018e-02, 2.319010893971509002e-01, 5.132800333608811272e-01, + 2.291778784481710018e-02, 2.291778784481710018e-02, 2.319010893971509002e-01, 5.132800333608811272e-01, + 5.132800333608811272e-01, 2.291778784481710018e-02, 2.319010893971509002e-01, 7.303134278075383845e-01, + 3.797004847182860326e-02, 3.797004847182860326e-02, 3.797004847182860326e-02, 7.303134278075383845e-01, + 3.797004847182860326e-02, 3.797004847182860326e-02, 3.797004847182860326e-02, 7.303134278075383845e-01, + 1.937464752488043951e-01, 3.797004847182860326e-02, 3.797004847182860326e-02, 3.797004847182860326e-02, + 1.937464752488043951e-01, 3.797004847182860326e-02, 3.797004847182860326e-02, 3.797004847182860326e-02, + 1.937464752488043951e-01, 3.797004847182860326e-02, 7.303134278075383845e-01, 1.937464752488043951e-01, + 7.303134278075383845e-01, 1.937464752488043951e-01, 3.797004847182860326e-02, 1.937464752488043951e-01, + 3.797004847182860326e-02, 7.303134278075383845e-01, 3.797004847182860326e-02, 1.937464752488043951e-01, + 7.303134278075383845e-01, 7.303134278075383845e-01, 3.797004847182860326e-02, 1.937464752488043951e-01, + 1.937464752488043951e-01, 7.303134278075383845e-01, 3.797004847182860326e-02 +}; +inline constexpr double tetrahedron_o8_weights[45] = { + -3.932700664129262086e-02, 4.081316059342699788e-03, 4.081316059342699788e-03, 4.081316059342699788e-03, + 4.081316059342699788e-03, 6.580867733043499175e-04, 6.580867733043499175e-04, 6.580867733043499175e-04, + 6.580867733043499175e-04, 4.384258825122849638e-03, 4.384258825122849638e-03, 4.384258825122849638e-03, + 4.384258825122849638e-03, 4.384258825122849638e-03, 4.384258825122849638e-03, 1.383006384250981720e-02, + 1.383006384250981720e-02, 1.383006384250981720e-02, 1.383006384250981720e-02, 1.383006384250981720e-02, + 1.383006384250981720e-02, 4.240437424683716691e-03, 4.240437424683716691e-03, 4.240437424683716691e-03, + 4.240437424683716691e-03, 4.240437424683716691e-03, 4.240437424683716691e-03, 4.240437424683716691e-03, + 4.240437424683716691e-03, 4.240437424683716691e-03, 4.240437424683716691e-03, 4.240437424683716691e-03, + 4.240437424683716691e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, + 2.238739739614200187e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, + 2.238739739614200187e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, 2.238739739614200187e-03, + 2.238739739614200187e-03 +}; + +// ---- order 9: 57 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o9_npts = 57; +inline constexpr int tetrahedron_o9_tdim = 3; +inline constexpr double tetrahedron_o9_points[171] = { + 6.891366839295158878e-03, 7.203807203359198219e-01, 2.440786635406753113e-01, 1.555789083027258879e-01, + 7.830238380676567411e-01, 3.722731406074435145e-02, 8.847735952623144717e-01, 3.746715582104113290e-02, + 4.313984945003825761e-02, 4.038577127605769146e-02, 2.233476678136009086e-01, 7.237240948070670088e-01, + 5.152850615572770465e-01, 4.405214048473231059e-01, 3.307435692542832345e-02, 3.474022441842899295e-02, + 8.739842844692826684e-01, 4.802973388725061826e-02, 2.737549691522870865e-01, 5.866194063678511483e-01, + 1.331737132533926116e-01, 3.138783669262656950e-02, 2.302476689782933031e-02, 2.010151099960877930e-01, + 4.195473200768754657e-02, 4.206648540987850682e-02, 8.751960265284055573e-01, 2.133537804622215872e-01, + 1.492382397847685956e-02, 7.276112298306399717e-01, 5.137368203860852300e-02, 7.378984493750679885e-01, + 1.052487006572960966e-02, 1.167918776735336027e-02, 3.967712051591504308e-02, 4.731716414318899999e-01, + 6.123408021820864189e-01, 1.432143403978234086e-01, 6.662179087437572001e-04, 4.585901947602266659e-02, + 4.589832979249514749e-02, 3.930065125704704232e-02, 1.030115138063945057e-01, 2.313142488715317913e-01, + 2.864577988396536153e-03, 8.373268951130939317e-03, 2.183035505483950089e-01, 6.383760811337779728e-02, + 7.518893444883035793e-01, 3.020971497018418161e-02, 3.931328269724702201e-02, 2.307731120717098905e-01, + 3.171156153538288308e-02, 2.914913102984440993e-02, 5.807381258551988328e-03, 4.350661767994387086e-01, + 4.471841503422259168e-01, 3.807370349415272748e-01, 1.946216802654708866e-01, 4.205051742028664830e-01, + 2.760774398234713267e-01, 6.335347557774518856e-01, 2.029485866160777099e-02, 6.107660031735782924e-02, + 4.443452478104635839e-01, 4.748568828672349862e-01, 4.011137824044635342e-02, 3.313166695541810980e-02, + 7.136911240616969909e-01, 2.545934399020729003e-02, 1.809521324516701890e-01, 6.820586129684939047e-01, + 6.044262999606022646e-01, 1.758681325508172980e-02, 2.037168634602604944e-01, 7.098027195586388283e-01, + 4.483105915111013778e-02, 2.113179923591710085e-01, 7.304989942620458976e-01, 1.871472921145495016e-01, + 3.706986589769196289e-02, 5.007701133228332768e-01, 2.448223828136365104e-02, 4.933213970351708888e-02, + 3.332720320375656686e-02, 4.837323966974713119e-01, 3.722853145238250172e-02, 3.567521368987188191e-01, + 1.328834330395907981e-01, 2.051457140178871916e-02, 1.838549978176257926e-01, 2.094427630108402008e-02, + 1.704389178072653055e-01, 2.268373199643007876e-01, 4.167820833252521839e-01, 7.798392818596212861e-03, + 1.923717952701476941e-01, 1.242856487305182972e-01, 6.561382235043771161e-01, 3.619508813743886888e-01, + 1.552011599218285944e-02, 3.924336805270702833e-01, 1.965056042084037044e-02, 6.443583098389682462e-01, + 1.340945354491865082e-01, 4.582446695135359849e-01, 3.667546489013540079e-02, 4.635857264004116240e-01, + 1.538959981066759068e-02, 3.394322940952808065e-01, 2.418695119980842911e-01, 1.242356229882020935e-01, + 2.743271521385678979e-02, 4.125431242926855258e-01, 5.655163520825289281e-01, 2.158455335267193131e-01, + 1.861909388269384069e-01, 1.031571468282740933e-01, 6.454494634370022688e-01, 2.025517153954042027e-01, + 4.580873781449242704e-02, 1.306656875755305980e-01, 2.354771046476908936e-01, 2.222475737162643017e-01, + 3.744969790769868134e-01, 3.639253527626089002e-01, 4.587456757955503117e-01, 3.601567132115269798e-01, + 3.689983110212465001e-02, 3.633566801123643275e-01, 4.147619222990370780e-01, 1.625263637506013104e-01, + 1.839853909173692903e-01, 5.281258992885370929e-02, 5.791440724832207509e-01, 3.912033521689178839e-02, + 1.799045015970502925e-01, 4.765094010040438754e-01, 1.524683171735234932e-01, 1.476870681014710862e-01, + 9.407076219108001514e-02, 1.668749728706787894e-01, 5.729563685802239181e-01, 7.774426835124648161e-02, + 1.417161598745217965e-01, 2.271598693150327086e-01, 5.175989795285014239e-01, 3.516091771883740891e-01, + 5.503739382968048099e-02, 1.969410619376470994e-01, 3.364345271215076760e-01, 2.289840090706933007e-01, + 8.273350534732909489e-02, 1.096124583285899984e-01, 3.537003584345383977e-01, 1.179344061919235936e-01, + 5.571603219749478031e-01, 1.325043854190557091e-01, 1.224035951881698031e-01, 8.342378852063631056e-02, + 4.222286016719212776e-01, 3.031418645744978768e-01, 2.709662131081064795e-01, 3.336191905680228054e-01, + 1.998230301014543020e-01, 3.787443941211687259e-01, 1.434829224197070041e-01, 3.469408062547597038e-01, + 1.819772234774806041e-01, 1.650628409073056913e-01, 3.092464405790882886e-01 +}; +inline constexpr double tetrahedron_o9_weights[57] = { + 7.162016044045417919e-04, 9.077771144629986693e-04, 9.168420416400241803e-04, 9.750266138313077625e-04, + 1.100190092573179830e-03, 1.123566736937567714e-03, 1.171941882444346023e-03, 1.174462115726251314e-03, + 1.182281355818436434e-03, 1.184296682706247050e-03, 1.190006619366603541e-03, 1.199964370174360544e-03, + 1.313356829704150986e-03, 1.336868422542667259e-03, 1.366383380621510315e-03, 1.382705960475030154e-03, + 1.524428447483084856e-03, 1.527532399215677074e-03, 1.629900429024984691e-03, 1.637560184133700402e-03, + 1.718580977267058227e-03, 1.937492188901679966e-03, 1.960422756541076606e-03, 2.095484520412313392e-03, + 2.162543037223973313e-03, 2.164588522968261420e-03, 2.180978257180039934e-03, 2.205183817607956915e-03, + 2.212319341124884767e-03, 2.214375065161266771e-03, 2.279539234758831429e-03, 2.377741014223799711e-03, + 2.482591391785385088e-03, 2.519796498600750025e-03, 2.563955465818866693e-03, 2.619562878906951751e-03, + 2.745898463014619789e-03, 3.314801767761809889e-03, 3.559049951653051394e-03, 3.730118502877720083e-03, + 3.834914870295061742e-03, 4.215437802807641375e-03, 4.232085234391223458e-03, 4.275313663630222159e-03, + 4.380082113958086443e-03, 4.395931402994279931e-03, 4.687869774289419797e-03, 5.307550768351452196e-03, + 5.439951471336866400e-03, 5.520179070379666562e-03, 5.870060438901905132e-03, 5.879894123846905046e-03, + 5.952755449705821739e-03, 6.370180985658903915e-03, 6.979224618881529375e-03, 7.535230513202574587e-03, + 8.183687426958101072e-03 +}; + +// ---- order 10: 74 point(s), tdim=3 ---- +inline constexpr int tetrahedron_o10_npts = 74; +inline constexpr int tetrahedron_o10_tdim = 3; +inline constexpr double tetrahedron_o10_points[222] = { + 4.844889527768170973e-03, 4.844889527573281729e-03, 9.854653314167374223e-01, 9.467198482923540048e-01, + 6.667080448351053908e-03, 8.067505561904879385e-03, 6.667080448658991611e-03, 3.854556569737443289e-02, + 8.067505561779885620e-03, 3.854556569748009837e-02, 9.467198482918521840e-01, 8.067505562173938466e-03, + 3.826891933029690201e-03, 8.740251327651676894e-01, 1.855431505435713863e-02, 1.035936602480028029e-01, + 3.826891933043923173e-03, 1.855431505433684930e-02, 8.740251327649323221e-01, 1.035936602475548002e-01, + 1.855431505434555067e-02, 1.735404744568466023e-02, 6.202583012900508841e-01, 3.240346370091629763e-02, + 6.202583012898867931e-01, 3.299841875634281774e-01, 3.240346370091293227e-02, 3.299841875637113953e-01, + 1.735404744578351172e-02, 3.240346370092631739e-02, 3.118131875220203977e-02, 1.266394908785818951e-01, + 8.114772294984898826e-01, 1.266394908788713025e-01, 3.070196087069637994e-02, 8.114772294981942302e-01, + 3.070196087074503893e-02, 3.118131875221085911e-02, 8.114772294979708533e-01, 7.805722458243883333e-01, + 3.518958226876812284e-02, 3.307359982554328171e-02, 3.518958226870846917e-02, 1.511645720816398863e-01, + 3.307359982557703942e-02, 1.511645720815177063e-01, 7.805722458241508566e-01, 3.307359982557291772e-02, + 8.149022829407979174e-01, 3.351563550542004805e-02, 1.193767951360694968e-01, 3.351563550541748759e-02, + 3.220528641772837930e-02, 1.193767951358614965e-01, 3.220528641771443906e-02, 8.149022829405031532e-01, + 1.193767951363346042e-01, 5.596600888554595921e-01, 1.807602495702272105e-01, 7.438795731167320915e-03, + 1.807602495700562084e-01, 2.521408658433209049e-01, 7.438795731199615395e-03, 2.521408658430335237e-01, + 5.596600888557026199e-01, 7.438795731214366616e-03, 3.029393775770430897e-02, 3.510309266619137114e-01, + 3.266058005326467201e-02, 3.510309266617718804e-01, 5.860145555272517903e-01, 3.266058005328976305e-02, + 5.860145555274774987e-01, 3.029393775772133007e-02, 3.266058005327201336e-02, 3.114461249008677879e-02, + 5.992738011392267916e-01, 3.365975263830275899e-01, 3.298405998767924702e-02, 3.114461249007869151e-02, + 3.365975263825117247e-01, 5.992738011395161157e-01, 3.298405998767670738e-02, 3.365975263827066799e-01, + 1.267643164854560973e-02, 1.645472158899153881e-01, 4.090752240119477912e-01, 1.645472158900110893e-01, + 4.137011284494345187e-01, 4.090752240120062999e-01, 4.137011284495361041e-01, 1.267643164855133953e-02, + 4.090752240118714078e-01, 3.573908441154557275e-02, 3.377912559111834190e-02, 5.925328701707139878e-01, + 3.379489198264274963e-01, 3.573908441154142329e-02, 5.925328701708977297e-01, 3.377912559113958879e-02, + 3.379489198260952065e-01, 5.925328701712223589e-01, 1.336599495323922970e-01, 1.336599495326110942e-01, + 5.990201514025275120e-01, 1.669597693172062969e-01, 1.669597693175803033e-01, 4.991206920481123244e-01, + 1.228042856243440062e-02, 4.126008361791699275e-01, 3.979043621857172863e-01, 1.772143730727422095e-01, + 1.228042856244396068e-02, 3.979043621855937185e-01, 4.126008361792021240e-01, 1.772143730727064881e-01, + 3.979043621855659074e-01, 3.696126448561478095e-01, 1.145379863160246037e-01, 2.495402206716512000e-02, + 1.145379863158415001e-01, 4.908953467609971089e-01, 2.495402206723796104e-02, 4.908953467605529086e-01, + 3.696126448562169764e-01, 2.495402206723979985e-02, 8.302494688196357386e-02, 7.282094809974271055e-01, + 3.849678186899387761e-02, 7.282094809970688365e-01, 1.502687902517552132e-01, 3.849678186899534171e-02, + 1.502687902517038099e-01, 8.302494688216775776e-02, 3.849678186896474813e-02, 1.680959146959895914e-02, + 4.043770144883662154e-01, 1.613371421710750875e-01, 4.174762518709079773e-01, 1.680959146964861040e-02, + 1.613371421710811937e-01, 4.043770144883683804e-01, 4.174762518708102776e-01, 1.613371421711606024e-01, + 1.696138475604022922e-01, 6.339280343903017645e-01, 1.733608592737586884e-01, 2.309725877555647913e-02, + 1.696138475603946871e-01, 1.733608592737317933e-01, 6.339280343903569426e-01, 2.309725877556989895e-02, + 1.733608592736602116e-01, 2.434102832026447880e-02, 6.335875653142608588e-01, 1.650530553465074934e-01, + 1.770183510189466958e-01, 2.434102832027353058e-02, 1.650530553463749051e-01, 6.335875653143980824e-01, + 1.770183510189176912e-01, 1.650530553463994132e-01, 2.935511686037215948e-02, 1.740037654545786017e-01, + 6.208415104357217773e-01, 1.740037654546278123e-01, 1.757996072492150896e-01, 6.208415104357764003e-01, + 1.757996072493022977e-01, 2.935511686041814006e-02, 6.208415104356415082e-01, 1.121243864649791033e-01, + 5.325889236500517265e-01, 2.362419588457611019e-01, 1.190447310392824948e-01, 1.121243864650229988e-01, + 2.362419588456987907e-01, 5.325889236499357082e-01, 1.190447310392760000e-01, 2.362419588456607933e-01, + 1.193225934917415965e-01, 4.534859243589937083e-01, 1.285563923599583047e-01, 2.986350897893351175e-01, + 1.193225934919169978e-01, 1.285563923597632940e-01, 4.534859243587894828e-01, 2.986350897893639833e-01, + 1.285563923599528091e-01, 3.020019073266451137e-01, 3.283455214738412731e-01, 3.030261289420949078e-01, + 3.283455214738281169e-01, 6.662644225740220472e-02, 3.030261289418548776e-01, 6.662644225731254033e-02, + 3.020019073267440901e-01, 3.030261289420781989e-01, 5.159168166142744649e-01, 1.324670107447714906e-01, + 1.074464615850584948e-01, 1.324670107446420941e-01, 2.441697110559039874e-01, 1.074464615851540988e-01, + 2.441697110556645955e-01, 5.159168166144414425e-01, 1.074464615851919019e-01, 3.182062008203240255e-01, + 3.182062008205924220e-01, 4.538139753862874964e-02, 1.207131116358086953e-01, 3.395130626211381175e-01, + 4.110926574948671197e-01, 3.395130626209016955e-01, 1.286811682481305008e-01, 4.110926574949583245e-01, + 1.286811682481935060e-01, 1.207131116358556022e-01, 4.110926574950111156e-01, 2.570719807624278208e-01, + 2.570719807625507225e-01, 2.287840577124626018e-01 +}; +inline constexpr double tetrahedron_o10_weights[74] = { + 6.365271938317999887e-05, 1.316831857909543505e-04, 1.316831857919753166e-04, 1.316831857927753494e-04, + 2.820943065241141826e-04, 2.820943065245265047e-04, 2.820943065259753240e-04, 1.023192373304246264e-03, + 1.023192373307521205e-03, 1.023192373307959873e-03, 1.103390983747504781e-03, 1.103390983748027366e-03, + 1.103390983748753782e-03, 1.164160167499589957e-03, 1.164160167500396820e-03, 1.164160167500605204e-03, + 1.191461909106608999e-03, 1.191461909106978278e-03, 1.191461909107789695e-03, 1.368674554135069056e-03, + 1.368674554136908731e-03, 1.368674554137219246e-03, 1.522996656033314029e-03, 1.522996656034059960e-03, + 1.522996656034905421e-03, 1.695831015223938287e-03, 1.695831015224264849e-03, 1.695831015225120067e-03, + 1.873698558482353276e-03, 1.873698558483606613e-03, 1.873698558484433209e-03, 1.898703097503021782e-03, + 1.898703097503796552e-03, 1.898703097504038330e-03, 1.902828894582963117e-03, 1.959184315344778601e-03, + 1.969493864818561927e-03, 1.969493864819973124e-03, 1.969493864825328216e-03, 2.064660777657845083e-03, + 2.064660777661681424e-03, 2.064660777662241740e-03, 2.083913166714165214e-03, 2.083913166715284978e-03, + 2.083913166716659746e-03, 2.094685984369893100e-03, 2.094685984373114915e-03, 2.094685984375275079e-03, + 2.191419694439080031e-03, 2.191419694439428276e-03, 2.191419694440678145e-03, 2.284450780288395030e-03, + 2.284450780289186498e-03, 2.284450780289794952e-03, 2.777404909175530245e-03, 2.777404909176179899e-03, + 2.777404909179498425e-03, 3.445098889335028100e-03, 3.445098889341516400e-03, 3.445098889342071511e-03, + 4.234512641858648001e-03, 4.234512641859506689e-03, 4.234512641860642933e-03, 4.471157472950426127e-03, + 4.471157472951478237e-03, 4.471157472952013399e-03, 4.553883199161186564e-03, 4.553883199161758155e-03, + 4.553883199163368846e-03, 4.651927948037352760e-03, 4.682093742486136828e-03, 4.682093742490215163e-03, + 4.682093742491071249e-03, 7.763086997403201468e-03 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/generated/quadrature_tables_triangle.h b/cpp/src/generated/quadrature_tables_triangle.h new file mode 100644 index 0000000..728d4e9 --- /dev/null +++ b/cpp/src/generated/quadrature_tables_triangle.h @@ -0,0 +1,181 @@ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix 0.11.0.dev0 quadrature rules for 'triangle', orders 1..10. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py 10 +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{ +// ---- order 1: 1 point(s), tdim=2 ---- +inline constexpr int triangle_o1_npts = 1; +inline constexpr int triangle_o1_tdim = 2; +inline constexpr double triangle_o1_points[2] = { + 3.333333333333333148e-01, 3.333333333333333148e-01 +}; +inline constexpr double triangle_o1_weights[1] = { + 5.000000000000000000e-01 +}; + +// ---- order 2: 3 point(s), tdim=2 ---- +inline constexpr int triangle_o2_npts = 3; +inline constexpr int triangle_o2_tdim = 2; +inline constexpr double triangle_o2_points[6] = { + 1.666666666666666574e-01, 1.666666666666666574e-01, 1.666666666666666574e-01, 6.666666666666666297e-01, + 6.666666666666666297e-01, 1.666666666666666574e-01 +}; +inline constexpr double triangle_o2_weights[3] = { + 1.666666666666666574e-01, 1.666666666666666574e-01, 1.666666666666666574e-01 +}; + +// ---- order 3: 6 point(s), tdim=2 ---- +inline constexpr int triangle_o3_npts = 6; +inline constexpr int triangle_o3_tdim = 2; +inline constexpr double triangle_o3_points[12] = { + 6.590276223740919592e-01, 2.319333685530310107e-01, 6.590276223740919592e-01, 1.090390090728770023e-01, + 2.319333685530310107e-01, 6.590276223740919592e-01, 2.319333685530310107e-01, 1.090390090728770023e-01, + 1.090390090728770023e-01, 6.590276223740919592e-01, 1.090390090728770023e-01, 2.319333685530310107e-01 +}; +inline constexpr double triangle_o3_weights[6] = { + 8.333333333333332871e-02, 8.333333333333332871e-02, 8.333333333333332871e-02, 8.333333333333332871e-02, + 8.333333333333332871e-02, 8.333333333333332871e-02 +}; + +// ---- order 4: 6 point(s), tdim=2 ---- +inline constexpr int triangle_o4_npts = 6; +inline constexpr int triangle_o4_tdim = 2; +inline constexpr double triangle_o4_points[12] = { + 8.168475729804589580e-01, 9.157621350977100672e-02, 9.157621350977100672e-02, 8.168475729804589580e-01, + 9.157621350977100672e-02, 9.157621350977100672e-02, 1.081030181680699975e-01, 4.459484909159650012e-01, + 4.459484909159650012e-01, 1.081030181680699975e-01, 4.459484909159650012e-01, 4.459484909159650012e-01 +}; +inline constexpr double triangle_o4_weights[6] = { + 5.497587182766099784e-02, 5.497587182766099784e-02, 5.497587182766099784e-02, 1.116907948390055000e-01, + 1.116907948390055000e-01, 1.116907948390055000e-01 +}; + +// ---- order 5: 7 point(s), tdim=2 ---- +inline constexpr int triangle_o5_npts = 7; +inline constexpr int triangle_o5_tdim = 2; +inline constexpr double triangle_o5_points[14] = { + 3.333333333333333148e-01, 3.333333333333333148e-01, 7.974269853530872032e-01, 1.012865073234563290e-01, + 1.012865073234563290e-01, 7.974269853530872032e-01, 1.012865073234563290e-01, 1.012865073234563290e-01, + 5.971587178976980881e-02, 4.701420641051150540e-01, 4.701420641051150540e-01, 5.971587178976980881e-02, + 4.701420641051150540e-01, 4.701420641051150540e-01 +}; +inline constexpr double triangle_o5_weights[7] = { + 1.125000000000000028e-01, 6.296959027241358364e-02, 6.296959027241358364e-02, 6.296959027241358364e-02, + 6.619707639425308210e-02, 6.619707639425308210e-02, 6.619707639425308210e-02 +}; + +// ---- order 6: 12 point(s), tdim=2 ---- +inline constexpr int triangle_o6_npts = 12; +inline constexpr int triangle_o6_tdim = 2; +inline constexpr double triangle_o6_points[24] = { + 8.738219710169959908e-01, 6.308901449150200458e-02, 6.308901449150200458e-02, 8.738219710169959908e-01, + 6.308901449150200458e-02, 6.308901449150200458e-02, 5.014265096581790315e-01, 2.492867451709100124e-01, + 2.492867451709100124e-01, 5.014265096581790315e-01, 2.492867451709100124e-01, 2.492867451709100124e-01, + 6.365024991213990013e-01, 3.103524510337850040e-01, 6.365024991213990013e-01, 5.314504984481600164e-02, + 3.103524510337850040e-01, 6.365024991213990013e-01, 3.103524510337850040e-01, 5.314504984481600164e-02, + 5.314504984481600164e-02, 6.365024991213990013e-01, 5.314504984481600164e-02, 3.103524510337850040e-01 +}; +inline constexpr double triangle_o6_weights[12] = { + 2.542245318510349961e-02, 2.542245318510349961e-02, 2.542245318510349961e-02, 5.839313786318949678e-02, + 5.839313786318949678e-02, 5.839313786318949678e-02, 4.142553780918700052e-02, 4.142553780918700052e-02, + 4.142553780918700052e-02, 4.142553780918700052e-02, 4.142553780918700052e-02, 4.142553780918700052e-02 +}; + +// ---- order 7: 15 point(s), tdim=2 ---- +inline constexpr int triangle_o7_npts = 15; +inline constexpr int triangle_o7_tdim = 2; +inline constexpr double triangle_o7_points[30] = { + 4.731956536892510390e-01, 4.731956536892510390e-01, 5.779764005450649389e-02, 5.779764005450649389e-02, + 2.416636063972474324e-01, 2.416636063972474324e-01, 4.731956536892510390e-01, 5.360869262149792203e-02, + 5.779764005450649389e-02, 8.844047198909870122e-01, 2.416636063972474324e-01, 5.166727872055051352e-01, + 5.360869262149792203e-02, 4.731956536892510390e-01, 8.844047198909870122e-01, 5.779764005450649389e-02, + 5.166727872055051352e-01, 2.416636063972474324e-01, 4.697120613008553408e-02, 2.593390118657856735e-01, + 6.936897820041287854e-01, 4.697120613008553408e-02, 2.593390118657856735e-01, 6.936897820041287854e-01, + 2.593390118657856735e-01, 4.697120613008553408e-02, 6.936897820041287854e-01, 2.593390118657856735e-01, + 4.697120613008553408e-02, 6.936897820041287854e-01 +}; +inline constexpr double triangle_o7_weights[15] = { + 2.659041664838023011e-02, 2.045908519702843362e-02, 6.386262428056692364e-02, 2.659041664838023011e-02, + 2.045908519702843362e-02, 6.386262428056692364e-02, 2.659041664838023011e-02, 2.045908519702843362e-02, + 6.386262428056692364e-02, 2.787727027034554717e-02, 2.787727027034554717e-02, 2.787727027034554717e-02, + 2.787727027034554717e-02, 2.787727027034554717e-02, 2.787727027034554717e-02 +}; + +// ---- order 8: 16 point(s), tdim=2 ---- +inline constexpr int triangle_o8_npts = 16; +inline constexpr int triangle_o8_tdim = 2; +inline constexpr double triangle_o8_points[32] = { + 3.333333333333333148e-01, 3.333333333333333148e-01, 1.705693077517602685e-01, 1.705693077517602685e-01, + 4.592925882927231251e-01, 4.592925882927231251e-01, 5.054722831703106767e-02, 5.054722831703106767e-02, + 1.705693077517602685e-01, 6.588613844964794630e-01, 4.592925882927231251e-01, 8.141482341455374971e-02, + 5.054722831703106767e-02, 8.989055433659378647e-01, 6.588613844964794630e-01, 1.705693077517602685e-01, + 8.141482341455374971e-02, 4.592925882927231251e-01, 8.989055433659378647e-01, 5.054722831703106767e-02, + 8.394777409957674547e-03, 2.631128296346380568e-01, 7.284923929554043553e-01, 8.394777409957674547e-03, + 2.631128296346380568e-01, 7.284923929554043553e-01, 2.631128296346380568e-01, 8.394777409957674547e-03, + 7.284923929554043553e-01, 2.631128296346380568e-01, 8.394777409957674547e-03, 7.284923929554043553e-01 +}; +inline constexpr double triangle_o8_weights[16] = { + 7.215780383889359995e-02, 5.160868526735912232e-02, 4.754581713364231660e-02, 1.622924881159903965e-02, + 5.160868526735912232e-02, 4.754581713364231660e-02, 1.622924881159903965e-02, 5.160868526735912232e-02, + 4.754581713364231660e-02, 1.622924881159903965e-02, 1.361515708721749811e-02, 1.361515708721749811e-02, + 1.361515708721749811e-02, 1.361515708721749811e-02, 1.361515708721749811e-02, 1.361515708721749811e-02 +}; + +// ---- order 9: 19 point(s), tdim=2 ---- +inline constexpr int triangle_o9_npts = 19; +inline constexpr int triangle_o9_tdim = 2; +inline constexpr double triangle_o9_points[38] = { + 3.333333333333333148e-01, 3.333333333333333148e-01, 4.896825191987376202e-01, 4.896825191987376202e-01, + 1.882035356190328024e-01, 1.882035356190328024e-01, 4.370895914929366355e-01, 4.370895914929366355e-01, + 4.472951339445274677e-02, 4.472951339445274677e-02, 4.896825191987376202e-01, 2.063496160252475953e-02, + 1.882035356190328024e-01, 6.235929287619343953e-01, 4.370895914929366355e-01, 1.258208170141267290e-01, + 4.472951339445274677e-02, 9.105409732110945065e-01, 2.063496160252475953e-02, 4.896825191987376202e-01, + 6.235929287619343953e-01, 1.882035356190328024e-01, 1.258208170141267290e-01, 4.370895914929366355e-01, + 9.105409732110945065e-01, 4.472951339445274677e-02, 3.683841205473629976e-02, 2.219629891607657057e-01, + 7.411985987844980084e-01, 3.683841205473629976e-02, 2.219629891607657057e-01, 7.411985987844980084e-01, + 2.219629891607657057e-01, 3.683841205473629976e-02, 7.411985987844980084e-01, 2.219629891607657057e-01, + 3.683841205473629976e-02, 7.411985987844980084e-01 +}; +inline constexpr double triangle_o9_weights[19] = { + 4.856789814139941819e-02, 1.566735011356953575e-02, 3.982386946360513130e-02, 3.891377050238713914e-02, + 1.278883782934901736e-02, 1.566735011356953575e-02, 3.982386946360513130e-02, 3.891377050238713914e-02, + 1.278883782934901736e-02, 1.566735011356953575e-02, 3.982386946360513130e-02, 3.891377050238713914e-02, + 1.278883782934901736e-02, 2.164176968864468809e-02, 2.164176968864468809e-02, 2.164176968864468809e-02, + 2.164176968864468809e-02, 2.164176968864468809e-02, 2.164176968864468809e-02 +}; + +// ---- order 10: 25 point(s), tdim=2 ---- +inline constexpr int triangle_o10_npts = 25; +inline constexpr int triangle_o10_tdim = 2; +inline constexpr double triangle_o10_points[50] = { + 3.333333333333333148e-01, 3.333333333333333148e-01, 4.951734598011705013e-01, 4.951734598011705013e-01, + 1.913941524284129558e-02, 1.913941524284129558e-02, 1.844850126852465300e-01, 1.844850126852465300e-01, + 4.282348209437188413e-01, 4.282348209437188413e-01, 4.951734598011705013e-01, 9.653080397658997391e-03, + 1.913941524284129558e-02, 9.617211695143174088e-01, 1.844850126852465300e-01, 6.310299746295069401e-01, + 4.282348209437188413e-01, 1.435303581125623174e-01, 9.653080397658997391e-03, 4.951734598011705013e-01, + 9.617211695143174088e-01, 1.913941524284129558e-02, 6.310299746295069401e-01, 1.844850126852465300e-01, + 1.435303581125623174e-01, 4.282348209437188413e-01, 3.472362048232748022e-02, 1.337347551008691293e-01, + 3.758272734119168929e-02, 3.266931362813368933e-01, 8.315416244168034599e-01, 3.472362048232748022e-02, + 6.357241363774713827e-01, 3.758272734119168929e-02, 1.337347551008691293e-01, 8.315416244168034599e-01, + 3.266931362813368933e-01, 6.357241363774713827e-01, 1.337347551008691293e-01, 3.472362048232748022e-02, + 3.266931362813368933e-01, 3.758272734119168929e-02, 8.315416244168034599e-01, 1.337347551008691293e-01, + 6.357241363774713827e-01, 3.266931362813368933e-01, 3.472362048232748022e-02, 8.315416244168034599e-01, + 3.758272734119168929e-02, 6.357241363774713827e-01 +}; +inline constexpr double triangle_o10_weights[25] = { + 4.180743718698696348e-02, 4.896295249209151740e-03, 3.192679615059327170e-03, 3.931688487318863584e-02, + 3.762366398427199193e-02, 4.896295249209151740e-03, 3.192679615059327170e-03, 3.931688487318863584e-02, + 3.762366398427199193e-02, 4.896295249209151740e-03, 3.192679615059327170e-03, 3.931688487318863584e-02, + 3.762366398427199193e-02, 1.448114073162817123e-02, 1.936952454300945245e-02, 1.448114073162817123e-02, + 1.936952454300945245e-02, 1.448114073162817123e-02, 1.936952454300945245e-02, 1.448114073162817123e-02, + 1.936952454300945245e-02, 1.448114073162817123e-02, 1.936952454300945245e-02, 1.448114073162817123e-02, + 1.936952454300945245e-02 +}; + +} // namespace cutcells::quadrature::generated diff --git a/cpp/src/ho_cut_mesh.cpp b/cpp/src/ho_cut_mesh.cpp new file mode 100644 index 0000000..2dd46d2 --- /dev/null +++ b/cpp/src/ho_cut_mesh.cpp @@ -0,0 +1,743 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "ho_cut_mesh.h" +#include "cell_certification.h" +#include "edge_certification.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +/// Gather vertex level-set values for the given cell. +template +void gather_vertex_ls_values(const MeshView& mesh, + const LevelSetFunction& ls, + I cell_id, int nv, + std::vector& out) +{ + out.resize(static_cast(nv)); + + if (ls.has_dof_values() && ls.has_mesh_data()) + { + auto dofs = ls.mesh_data->cell_dofs_span(cell_id); + for (int v = 0; v < nv; ++v) + out[static_cast(v)] = + ls.dof_values[static_cast( + dofs[static_cast(v)])]; + } + else if (ls.has_value()) + { + for (int v = 0; v < nv; ++v) + { + const I node_id = mesh.cell_node(cell_id, static_cast(v)); + out[static_cast(v)] = + ls.value(mesh.node(node_id), cell_id); + } + } + else + { + throw std::runtime_error( + "cut: LevelSetFunction has neither nodal_values, " + "dof_values, nor an analytical value function"); + } +} + +template +T bernstein_cell_sign_tol(std::span coeffs) +{ + T max_abs = 0; + for (const T c : coeffs) + max_abs = std::max(max_abs, std::fabs(c)); + + return std::max(T(64) * std::numeric_limits::epsilon() + * std::max(T(1), max_abs), + T(1e-14)); +} + +template +cell::domain classify_cell_domain_from_bernstein(std::span coeffs) +{ + if (coeffs.empty()) + return cell::domain::unset; + + const T sign_tol = bernstein_cell_sign_tol(coeffs); + if (bernstein_all_positive(coeffs, sign_tol)) + return cell::domain::outside; + if (bernstein_all_negative(coeffs, sign_tol)) + return cell::domain::inside; + return cell::domain::intersected; +} + +template +cell::domain classify_cell_domain_fast(const MeshView& mesh, + const LevelSetFunction& ls, + I cell_id, int nv, + bool use_bernstein_classification, + std::vector& vertex_ls_values, + LevelSetCell* intersected_ls_cell) +{ + if (use_bernstein_classification) + { + LevelSetCell ls_cell = make_cell_level_set(ls, cell_id); + const cell::domain dom = classify_cell_domain_from_bernstein( + std::span(ls_cell.bernstein_coeffs.data(), + ls_cell.bernstein_coeffs.size())); + if (dom != cell::domain::unset) + { + if (dom == cell::domain::intersected + && intersected_ls_cell != nullptr) + { + *intersected_ls_cell = std::move(ls_cell); + } + return dom; + } + } + + gather_vertex_ls_values(mesh, ls, cell_id, nv, vertex_ls_values); + return cell::classify_cell_domain( + std::span(vertex_ls_values.data(), + static_cast(nv))); +} + +template +bool cell_contains_all_vertices(std::span cell_verts, + std::span entity_verts) +{ + for (const int v : entity_verts) + { + bool found = false; + for (const auto cv : cell_verts) + { + if (cv == v) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; +} + +template +bool vertex_state_for_level_set(const AdaptCell& ac, + int vertex_id, + int level_set_id, + bool& is_negative, + bool& is_positive, + bool& is_zero) +{ + if (level_set_id < 0 || level_set_id >= 64) + return false; + + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + const auto zm = ac.zero_mask_per_vertex[static_cast(vertex_id)]; + const auto nm = ac.negative_mask_per_vertex[static_cast(vertex_id)]; + is_zero = (zm & bit) != 0; + is_negative = !is_zero && ((nm & bit) != 0); + is_positive = !is_zero && !is_negative; + return true; +} + +template +bool leaf_cell_matches_sign_requirements( + const AdaptCell& ac, + std::span cell_verts, + const SelectionTerm& term, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + const int nls = std::min(bg.num_level_sets, 64); + for (int li = 0; li < nls; ++li) + { + const std::uint64_t bit = std::uint64_t(1) << li; + const bool require_neg = (term.negative_required & bit) != 0; + const bool require_pos = (term.positive_required & bit) != 0; + if (!require_neg && !require_pos) + continue; + + const bool ls_is_active = (cut_cell_active_mask & bit) != 0; + if (!ls_is_active) + { + const auto dom = bg.domain(li, parent_cell_id); + if (require_neg && dom != cell::domain::inside) + return false; + if (require_pos && dom != cell::domain::outside) + return false; + continue; + } + + bool has_neg = false; + bool has_pos = false; + for (const auto cv : cell_verts) + { + bool is_neg = false; + bool is_pos = false; + bool is_zero = false; + vertex_state_for_level_set(ac, static_cast(cv), li, is_neg, is_pos, is_zero); + has_neg = has_neg || is_neg; + has_pos = has_pos || is_pos; + } + + if (require_neg) + { + if (has_pos || !has_neg) + return false; + } + if (require_pos) + { + if (has_neg || !has_pos) + return false; + } + } + + return true; +} + +template +bool leaf_cell_matches_selection_expr( + const AdaptCell& ac, + std::span cell_verts, + const SelectionExpr& expr, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + for (const auto& term : expr.terms) + { + if (leaf_cell_matches_sign_requirements( + ac, cell_verts, term, cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + return false; +} + +template +bool zero_entity_matches( + const AdaptCell& ac, + int zero_entity_index, + int target_dim, + const SelectionTerm& term, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + if (ac.zero_entity_dim[static_cast(zero_entity_index)] != target_dim) + return false; + + const auto zero_mask = ac.zero_entity_zero_mask[static_cast(zero_entity_index)]; + if ((cut_cell_active_mask & term.zero_required) != term.zero_required) + return false; + + if ((zero_mask & term.zero_required) != term.zero_required) + return false; + + if (term.negative_required == 0 && term.positive_required == 0) + return true; + + std::vector zero_verts; + const int zdim = ac.zero_entity_dim[static_cast(zero_entity_index)]; + const int zid = ac.zero_entity_id[static_cast(zero_entity_index)]; + if (zdim == 0) + { + zero_verts.push_back(zid); + } + else + { + auto verts = ac.entity_to_vertex[zdim][static_cast(zid)]; + zero_verts.reserve(verts.size()); + for (const auto v : verts) + zero_verts.push_back(static_cast(v)); + } + + const int tdim = ac.tdim; + const int n_cells = ac.n_entities(tdim); + for (int c = 0; c < n_cells; ++c) + { + auto cell_verts = ac.entity_to_vertex[tdim][static_cast(c)]; + if (!cell_contains_all_vertices(cell_verts, std::span(zero_verts))) + continue; + + if (leaf_cell_matches_sign_requirements( + ac, cell_verts, term, cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + + return false; +} + +template +bool zero_entity_matches_selection_expr( + const AdaptCell& ac, + int zero_entity_index, + int target_dim, + const SelectionExpr& expr, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + for (const auto& term : expr.terms) + { + if (zero_entity_matches( + ac, zero_entity_index, target_dim, term, + cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + return false; +} + +template +void refresh_adapt_cell_semantics( + AdaptCell& ac, + std::span processed_level_set_indices, + std::span> processed_level_set_cells, + int total_num_level_sets, + T zero_tol) +{ + for (std::size_t i = 0; i < processed_level_set_indices.size(); ++i) + { + fill_all_vertex_signs_from_level_set( + ac, + processed_level_set_cells[i], + processed_level_set_indices[i], + zero_tol); + } + + recompute_active_level_set_masks(ac, total_num_level_sets); +} + +} // anonymous namespace + +// ===================================================================== +// cut() — single level set +// ===================================================================== + +template +std::pair, BackgroundMeshData> +cut(const MeshView& mesh, + const LevelSetFunction& ls, + bool triangulate_cut_parts) +{ + if (!mesh.has_cell_types()) + throw std::runtime_error("cut: MeshView must have cell types"); + + const I ncells = mesh.num_cells(); + + // --- BackgroundMeshData --- + BackgroundMeshData bg; + bg.mesh = &mesh; + bg.level_set_names.push_back(ls.name); + bg.num_cells = static_cast(ncells); + bg.num_level_sets = 1; + bg.cell_domains.assign(static_cast(ncells), + cell::domain::unset); + bg.cell_to_cut_index.assign(static_cast(ncells), -1); + + // --- HOCutCells --- + HOCutCells hc; + hc.gdim = mesh.gdim; + hc.tdim = cell::get_tdim(mesh.cell_type(I(0))); + hc.ls_offsets.push_back(0); + + const bool use_bernstein_classification = + ls.type == LevelSetType::Polynomial + && ls.has_mesh_data() + && ls.has_dof_values(); + std::vector ls_vertex_vals; + + for (I ci = 0; ci < ncells; ++ci) + { + const cell::type ctype = mesh.cell_type(ci); + const int nv = cell::get_num_vertices(ctype); + LevelSetCell ls_cell; + const cell::domain dom = classify_cell_domain_fast( + mesh, ls, ci, nv, use_bernstein_classification, ls_vertex_vals, + &ls_cell); + bg.cell_domains[static_cast(ci)] = dom; + + if (dom != cell::domain::intersected) + continue; + + const int cut_idx = hc.num_cut_cells(); + bg.cell_to_cut_index[static_cast(ci)] = cut_idx; + + // LevelSetCell + hc.level_set_cells.push_back(std::move(ls_cell)); + hc.ls_offsets.push_back( + static_cast(hc.level_set_cells.size())); + + // AdaptCell + AdaptCell ac = make_adapt_cell(mesh, ci); + + certify_refine_and_process_ready_cells( + ac, hc.level_set_cells.back(), /*level_set_id=*/0, + /*max_iterations=*/8, T(1e-12), T(1e-12), /*edge_max_depth=*/20, + triangulate_cut_parts); + { + const std::array processed_ids = {0}; + const auto* processed_cell = &hc.level_set_cells.back(); + refresh_adapt_cell_semantics( + ac, + std::span(processed_ids.data(), processed_ids.size()), + std::span>(processed_cell, std::size_t(1)), + /*total_num_level_sets=*/1, + T(1e-12)); + } + + // Finalize derived topology/semantic layers used by selection + output. + build_edges(ac); + if (ac.tdim == 3) + build_faces(ac); + rebuild_zero_entity_inventory(ac); + hc.adapt_cells.push_back(std::move(ac)); + + // Single LS: bit 0 is always set. + hc.active_level_set_mask.push_back(std::uint64_t(1)); + + hc.parent_cell_ids.push_back(ci); + } + + return {std::move(hc), std::move(bg)}; +} + +// ===================================================================== +// cut() — multiple level sets +// ===================================================================== + +template +std::pair, BackgroundMeshData> +cut(const MeshView& mesh, + const std::vector>& level_sets, + bool triangulate_cut_parts) +{ + if (!mesh.has_cell_types()) + throw std::runtime_error("cut: MeshView must have cell types"); + if (level_sets.empty()) + throw std::runtime_error("cut: no level sets provided"); + if (level_sets.size() > 64) + throw std::runtime_error("cut: more than 64 level sets not supported"); + + const I ncells = mesh.num_cells(); + const int nls = static_cast(level_sets.size()); + + // --- BackgroundMeshData --- + BackgroundMeshData bg; + bg.mesh = &mesh; + bg.num_cells = static_cast(ncells); + bg.num_level_sets = nls; + bg.cell_domains.assign( + static_cast(nls) * static_cast(ncells), + cell::domain::unset); + bg.cell_to_cut_index.assign(static_cast(ncells), -1); + for (const auto& ls : level_sets) + bg.level_set_names.push_back(ls.name); + + // --- HOCutCells --- + HOCutCells hc; + hc.gdim = mesh.gdim; + hc.tdim = cell::get_tdim(mesh.cell_type(I(0))); + hc.ls_offsets.push_back(0); + + std::vector use_bernstein_classification( + static_cast(nls), false); + for (int li = 0; li < nls; ++li) + { + const auto& ls = level_sets[static_cast(li)]; + use_bernstein_classification[static_cast(li)] = + ls.type == LevelSetType::Polynomial + && ls.has_mesh_data() + && ls.has_dof_values(); + } + std::vector ls_vertex_vals; + + for (I ci = 0; ci < ncells; ++ci) + { + const cell::type ctype = mesh.cell_type(ci); + const int nv = cell::get_num_vertices(ctype); + + // Classify each level set individually; track if any intersects. + bool any_intersected = false; + std::vector intersected_ls_indices; + std::vector> intersected_ls_cells; + intersected_ls_indices.reserve(static_cast(nls)); + intersected_ls_cells.reserve(static_cast(nls)); + for (int li = 0; li < nls; ++li) + { + LevelSetCell ls_cell; + const cell::domain dom = classify_cell_domain_fast( + mesh, level_sets[static_cast(li)], ci, nv, + use_bernstein_classification[static_cast(li)], + ls_vertex_vals, &ls_cell); + + bg.cell_domains[static_cast( + li * static_cast(ncells) + static_cast(ci))] = dom; + + if (dom == cell::domain::intersected) + { + any_intersected = true; + ls_cell.level_set_id = li; + intersected_ls_indices.push_back(li); + intersected_ls_cells.push_back(std::move(ls_cell)); + } + } + + if (!any_intersected) + continue; + + std::vector all_level_set_indices(static_cast(nls)); + std::iota(all_level_set_indices.begin(), all_level_set_indices.end(), 0); + std::vector> all_level_set_cells; + all_level_set_cells.reserve(static_cast(nls)); + for (int li = 0; li < nls; ++li) + { + auto ls_cell = make_cell_level_set(level_sets[static_cast(li)], ci); + ls_cell.level_set_id = li; + all_level_set_cells.push_back(std::move(ls_cell)); + } + + const int cut_idx = hc.num_cut_cells(); + bg.cell_to_cut_index[static_cast(ci)] = cut_idx; + + // Build AdaptCell once per cell. + AdaptCell ac = make_adapt_cell(mesh, ci); + + // Process intersecting level sets recursively (input order). + // + std::uint64_t cell_active_mask = 0; + for (std::size_t k = 0; k < intersected_ls_indices.size(); ++k) + { + const int li = intersected_ls_indices[k]; + certify_refine_and_process_ready_cells( + ac, intersected_ls_cells[k], li, + /*max_iterations=*/8, T(1e-12), T(1e-12), + /*edge_max_depth=*/20, triangulate_cut_parts); + + // New vertices created while processing level set li must be + // reclassified for all already-processed level sets. + refresh_adapt_cell_semantics( + ac, + std::span(intersected_ls_indices.data(), k + 1), + std::span>(intersected_ls_cells.data(), k + 1), + nls, + T(1e-12)); + + cell_active_mask |= std::uint64_t(1) << li; + } + + // Finalize derived topology/semantic layers used by selection + output. + build_edges(ac); + if (ac.tdim == 3) + build_faces(ac); + recompute_active_level_set_masks(ac, nls); + refresh_adapt_cell_semantics( + ac, + std::span(all_level_set_indices.data(), all_level_set_indices.size()), + std::span>(all_level_set_cells.data(), all_level_set_cells.size()), + nls, + T(1e-12)); + rebuild_zero_entity_inventory(ac); + + // Persist only level sets actively changing sign in this parent cell. + // Non-active level sets may still touch a vertex/face, but they do not + // change sign in this cut-cell. + for (auto& ls_cell : intersected_ls_cells) + hc.level_set_cells.push_back(std::move(ls_cell)); + hc.ls_offsets.push_back( + static_cast(hc.level_set_cells.size())); + hc.active_level_set_mask.push_back(cell_active_mask); + + hc.adapt_cells.push_back(std::move(ac)); + hc.parent_cell_ids.push_back(ci); + } + + return {std::move(hc), std::move(bg)}; +} + +// ===================================================================== +// select_part() +// ===================================================================== + +template +HOMeshPart select_part(const HOCutCells& cut_cells, + const BackgroundMeshData& bg, + std::string_view expr_str) +{ + HOMeshPart part; + part.cut_cells = &cut_cells; + part.bg = &bg; + + // Parse and compile the expression. + part.expr = parse_selection_expr(expr_str); + compile_selection_expr(part.expr, bg.level_set_names); + part.dim = infer_selection_dim(part.expr, cut_cells.tdim); + + const bool is_volume = (part.dim == cut_cells.tdim); + + // --- Uncut cells: scan cell_domains --- + const int ncells = bg.num_cells; + for (I ci = 0; ci < static_cast(ncells); ++ci) + { + if (bg.cell_to_cut_index[static_cast(ci)] >= 0) + continue; // cut cell, handled below + + // For an uncut cell, every vertex has the same sign for each LS. + // Check if the cell domain is compatible with the expression. + bool match = false; + for (const auto& term : part.expr.terms) + { + bool term_match = true; + for (const auto& clause : term.clauses) + { + const int li = clause.level_set_index; + const cell::domain dom = bg.domain(li, ci); + + switch (clause.relation) + { + case Relation::LessThan: + if (dom != cell::domain::inside) term_match = false; + break; + case Relation::GreaterThan: + if (dom != cell::domain::outside) term_match = false; + break; + case Relation::EqualTo: + // An uncut (non-intersected) cell has no zero interface. + term_match = false; + break; + } + if (!term_match) break; + } + if (term_match) + { + match = true; + break; + } + } + + if (match) + part.uncut_cell_ids.push_back(ci); + } + + // --- Cut cells --- + const int ncut = cut_cells.num_cut_cells(); + for (int k = 0; k < ncut; ++k) + { + const auto& ac = cut_cells.adapt_cells[static_cast(k)]; + const I bg_cell = cut_cells.parent_cell_ids[static_cast(k)]; + const std::uint64_t cut_active_mask = + cut_cells.active_level_set_mask[static_cast(k)]; + + if (is_volume) + { + bool has_matching_leaf = false; + const int tdim = ac.tdim; + const int n_leaf = ac.n_entities(tdim); + for (int c = 0; c < n_leaf; ++c) + { + auto cell_verts = ac.entity_to_vertex[tdim][static_cast(c)]; + if (leaf_cell_matches_selection_expr( + ac, cell_verts, part.expr, cut_active_mask, bg, bg_cell)) + { + has_matching_leaf = true; + break; + } + } + + if (has_matching_leaf) + part.cut_cell_ids.push_back(static_cast(k)); + } + else + { + bool has_matching_zero_entity = false; + const int n_zero = ac.n_zero_entities(); + for (int z = 0; z < n_zero; ++z) + { + if (zero_entity_matches_selection_expr( + ac, z, part.dim, part.expr, cut_active_mask, bg, bg_cell)) + { + has_matching_zero_entity = true; + break; + } + } + + if (has_matching_zero_entity) + part.cut_cell_ids.push_back(static_cast(k)); + } + } + + part.cut_only = !is_volume; + + return part; +} + +// --------------------------------------------------------------------------- +// Explicit template instantiations +// --------------------------------------------------------------------------- + +// cut() single LS +template std::pair, BackgroundMeshData> +cut(const MeshView&, const LevelSetFunction&, bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const LevelSetFunction&, bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const LevelSetFunction&, bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const LevelSetFunction&, bool); + +// cut() multi LS +template std::pair, BackgroundMeshData> +cut(const MeshView&, const std::vector>&, + bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const std::vector>&, + bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const std::vector>&, + bool); + +template std::pair, BackgroundMeshData> +cut(const MeshView&, const std::vector>&, + bool); + +// select_part() +template HOMeshPart +select_part(const HOCutCells&, const BackgroundMeshData&, + std::string_view); + +template HOMeshPart +select_part(const HOCutCells&, const BackgroundMeshData&, + std::string_view); + +template HOMeshPart +select_part(const HOCutCells&, const BackgroundMeshData&, + std::string_view); + +template HOMeshPart +select_part(const HOCutCells&, const BackgroundMeshData&, + std::string_view); + +} // namespace cutcells diff --git a/cpp/src/ho_cut_mesh.h b/cpp/src/ho_cut_mesh.h new file mode 100644 index 0000000..bdfdcc7 --- /dev/null +++ b/cpp/src/ho_cut_mesh.h @@ -0,0 +1,176 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include + +#include "adapt_cell.h" +#include "cell_flags.h" +#include "cell_certification.h" +#include "level_set.h" +#include "level_set_cell.h" +#include "mesh_view.h" +#include "selection_expr.h" + +namespace cutcells +{ + +// ===================================================================== +// BackgroundMeshData — mesh-wide metadata produced during cutting +// ===================================================================== + +/// Mesh-wide metadata produced during cutting. +/// +/// Holds a non-owning reference to the background mesh plus: +/// - level-set name registry +/// - per-cell, per-level-set domain classification +/// - cell_to_cut_index lookup +template +struct BackgroundMeshData +{ + /// Non-owning reference to the background mesh. + const MeshView* mesh = nullptr; + + /// Level-set name registry. + /// Index in this vector = bit position in AdaptCell bitmasks. + std::vector level_set_names; + + /// Per-cell, per-level-set domain classification. + /// Flat storage: cell_domains[ls_index * num_cells + cell_id]. + std::vector cell_domains; + int num_cells = 0; ///< = mesh->num_cells() + int num_level_sets = 0; + + /// Lookup: background cell id → index into HOCutCells arrays. + /// -1 for uncut cells. Size = num_cells. + std::vector cell_to_cut_index; + + /// Access the domain of a specific (level_set, cell) pair. + cell::domain domain(int ls_index, I cell_id) const + { + return cell_domains[static_cast( + ls_index * num_cells + static_cast(cell_id))]; + } +}; + +// ===================================================================== +// HOCutCells — pure cut-cell storage (intersected cells only) +// ===================================================================== + +/// Pure storage of intersected cells. +/// No mesh reference, no uncut cells, no classification. +template +struct HOCutCells +{ + int gdim = 0; + int tdim = 0; + + /// Background-mesh cell ids for the cut cells. + /// parent_cell_ids[k] indexes the background mesh for the k-th entry. + std::vector parent_cell_ids; + + /// AdaptCell (reference-space topology + vertex signs) for each cut cell. + std::vector> adapt_cells; + + /// Per cut-cell level-set data, flat CSR layout: + /// level_set_cells[ls_offsets[k] .. ls_offsets[k+1]] for k-th cut cell. + std::vector> level_set_cells; + std::vector ls_offsets; ///< size = num_cut_cells + 1 + + /// Bitmask of active (intersecting) level sets for each cut cell. + /// Bit li set ↔ level set li has domain::intersected for the k-th cut cell. + /// Size = num_cut_cells. + std::vector active_level_set_mask; + + /// Number of intersected cells. + int num_cut_cells() const + { + return static_cast(parent_cell_ids.size()); + } +}; + +// ===================================================================== +// HOMeshPart — pure view/extraction layer over HOCutCells + BackgroundMeshData +// ===================================================================== + +/// View over HOCutCells + BackgroundMeshData, filtered by a SelectionExpr. +/// +/// Stores no data of its own beyond the selection results. +template +struct HOMeshPart +{ + /// Non-owning references. + const HOCutCells* cut_cells = nullptr; + const BackgroundMeshData* bg = nullptr; + + /// The selection expression (compiled). + SelectionExpr expr; + + /// Inferred entity dimension of the selection. + int dim = -1; + + /// Indices into cut_cells->adapt_cells for cells with matching sub-entities. + std::vector cut_cell_ids; + + /// Background cell ids of uncut cells matching the selection. + std::vector uncut_cell_ids; + + /// true: iterate only cut cells (quadrature). + /// false: iterate both cut + uncut (visualization). + bool cut_only = false; +}; + +// ===================================================================== +// Factory: cut() — produces both HOCutCells + BackgroundMeshData +// ===================================================================== + +/// Build HOCutCells and BackgroundMeshData from a mesh and a single level set. +/// +/// Iterates all background cells, classifies each by a cheap Bernstein-cell +/// sign test when available (falling back to vertex signs otherwise), and +/// for intersected cells creates LevelSetCell + AdaptCell pairs. +/// +/// @param mesh Background mesh. Must have cell types. +/// @param ls Level-set function. +/// @return pair of (HOCutCells, BackgroundMeshData). +template +std::pair, BackgroundMeshData> +cut(const MeshView& mesh, + const LevelSetFunction& ls, + bool triangulate_cut_parts = false); + +/// Build HOCutCells and BackgroundMeshData from a mesh and multiple level sets. +/// +/// @param mesh Background mesh. +/// @param level_sets Vector of level-set functions. +/// @return pair of (HOCutCells, BackgroundMeshData). +template +std::pair, BackgroundMeshData> +cut(const MeshView& mesh, + const std::vector>& level_sets, + bool triangulate_cut_parts = true); + +// ===================================================================== +// select_part() — builds HOMeshPart +// ===================================================================== + +/// Build an HOMeshPart by filtering HOCutCells + BackgroundMeshData +/// with a selection expression string. +/// +/// @param cut_cells Intersected cell storage. +/// @param bg Mesh-wide metadata. +/// @param expr_str Selection expression, e.g. "phi < 0", "phi1 = 0 and phi2 < 0". +/// @return populated HOMeshPart. +template +HOMeshPart select_part(const HOCutCells& cut_cells, + const BackgroundMeshData& bg, + std::string_view expr_str); + +} // namespace cutcells diff --git a/cpp/src/ho_mesh_part_output.cpp b/cpp/src/ho_mesh_part_output.cpp new file mode 100644 index 0000000..77a2780 --- /dev/null +++ b/cpp/src/ho_mesh_part_output.cpp @@ -0,0 +1,824 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "ho_mesh_part_output.h" + +#include "cell_topology.h" +#include "mapping.h" +#include "quadrature_tables.h" +#include "reference_cell.h" +#include "triangulation.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace cutcells::output +{ +namespace +{ + +template +std::vector parent_cell_vertex_coords_vtk(const MeshView& mesh, + I cell_id) +{ + const auto ctype = mesh.cell_type(cell_id); + const int nv = cell::get_num_vertices(ctype); + std::vector coords(static_cast(nv * mesh.gdim), T(0)); + + for (int vtk_v = 0; vtk_v < nv; ++vtk_v) + { + const int local_v = mesh.vtk_vertex_order + ? vtk_v + : cell::vtk_to_basix_vertex(ctype, vtk_v); + const I node_id = mesh.cell_node(cell_id, static_cast(local_v)); + const T* x = mesh.node(node_id); + for (int d = 0; d < mesh.gdim; ++d) + coords[static_cast(vtk_v * mesh.gdim + d)] = x[d]; + } + return coords; +} + +inline bool is_simplex(cell::type cell_type) +{ + using cell::type; + return cell_type == type::point + || cell_type == type::interval + || cell_type == type::triangle + || cell_type == type::tetrahedron; +} + +inline cell::type simplex_type_for_dim(int dim) +{ + using cell::type; + switch (dim) + { + case 0: + return type::point; + case 1: + return type::interval; + case 2: + return type::triangle; + case 3: + return type::tetrahedron; + default: + throw std::runtime_error("Unsupported simplex dimension"); + } +} + +bool cell_contains_all_vertices(std::span cell_verts, + std::span entity_verts) +{ + for (const int v : entity_verts) + { + bool found = false; + for (const auto cv : cell_verts) + { + if (cv == v) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; +} + +template +bool vertex_state_for_level_set(const AdaptCell& ac, + int vertex_id, + int level_set_id, + bool& is_negative, + bool& is_positive, + bool& is_zero) +{ + if (level_set_id < 0 || level_set_id >= 64) + return false; + + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + const auto zm = ac.zero_mask_per_vertex[static_cast(vertex_id)]; + const auto nm = ac.negative_mask_per_vertex[static_cast(vertex_id)]; + is_zero = (zm & bit) != 0; + is_negative = !is_zero && ((nm & bit) != 0); + is_positive = !is_zero && !is_negative; + return true; +} + +template +bool leaf_cell_matches_sign_requirements( + const AdaptCell& ac, + std::span cell_verts, + const SelectionTerm& term, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + const int nls = std::min(bg.num_level_sets, 64); + for (int li = 0; li < nls; ++li) + { + const std::uint64_t bit = std::uint64_t(1) << li; + const bool require_neg = (term.negative_required & bit) != 0; + const bool require_pos = (term.positive_required & bit) != 0; + if (!require_neg && !require_pos) + continue; + + const bool ls_is_active = (cut_cell_active_mask & bit) != 0; + if (!ls_is_active) + { + const auto dom = bg.domain(li, parent_cell_id); + if (require_neg && dom != cell::domain::inside) + return false; + if (require_pos && dom != cell::domain::outside) + return false; + continue; + } + + bool has_neg = false; + bool has_pos = false; + for (const auto cv : cell_verts) + { + bool is_neg = false; + bool is_pos = false; + bool is_zero = false; + vertex_state_for_level_set( + ac, static_cast(cv), li, is_neg, is_pos, is_zero); + has_neg = has_neg || is_neg; + has_pos = has_pos || is_pos; + } + + if (require_neg && (has_pos || !has_neg)) + return false; + if (require_pos && (has_neg || !has_pos)) + return false; + } + + return true; +} + +template +bool leaf_cell_matches_selection_expr( + const AdaptCell& ac, + std::span cell_verts, + const SelectionExpr& expr, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + for (const auto& term : expr.terms) + { + if (leaf_cell_matches_sign_requirements( + ac, cell_verts, term, cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + return false; +} + +template +bool zero_entity_matches( + const AdaptCell& ac, + int zero_entity_index, + int target_dim, + const SelectionTerm& term, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + if (ac.zero_entity_dim[static_cast(zero_entity_index)] + != target_dim) + { + return false; + } + + const auto zero_mask = + ac.zero_entity_zero_mask[static_cast(zero_entity_index)]; + if ((cut_cell_active_mask & term.zero_required) != term.zero_required) + return false; + + if ((zero_mask & term.zero_required) != term.zero_required) + return false; + + if (term.negative_required == 0 && term.positive_required == 0) + return true; + + std::vector zero_verts; + const int zdim = ac.zero_entity_dim[static_cast(zero_entity_index)]; + const int zid = ac.zero_entity_id[static_cast(zero_entity_index)]; + if (zdim == 0) + { + zero_verts.push_back(zid); + } + else + { + auto verts = ac.entity_to_vertex[zdim][static_cast(zid)]; + zero_verts.reserve(verts.size()); + for (const auto v : verts) + zero_verts.push_back(static_cast(v)); + } + + const int tdim = ac.tdim; + const int n_cells = ac.n_entities(tdim); + for (int c = 0; c < n_cells; ++c) + { + auto cell_verts = ac.entity_to_vertex[tdim][static_cast(c)]; + if (!cell_contains_all_vertices( + cell_verts, + std::span(zero_verts.data(), zero_verts.size()))) + { + continue; + } + + if (leaf_cell_matches_sign_requirements( + ac, cell_verts, term, cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + + return false; +} + +template +bool zero_entity_matches_selection_expr( + const AdaptCell& ac, + int zero_entity_index, + int target_dim, + const SelectionExpr& expr, + std::uint64_t cut_cell_active_mask, + const BackgroundMeshData& bg, + I parent_cell_id) +{ + for (const auto& term : expr.terms) + { + if (zero_entity_matches( + ac, zero_entity_index, target_dim, term, + cut_cell_active_mask, bg, parent_cell_id)) + { + return true; + } + } + return false; +} + +struct SelectedEntity +{ + int local_zero_entity_id = -1; + cell::type type = cell::type::point; + std::vector vertices; +}; + +template +std::vector selected_entities(const HOMeshPart& part, + const AdaptCell& adapt_cell, + int cut_cell_id) +{ + const I parent_cell_id = + part.cut_cells->parent_cell_ids[static_cast(cut_cell_id)]; + const std::uint64_t cut_active_mask = + part.cut_cells->active_level_set_mask[static_cast(cut_cell_id)]; + + std::vector entities; + if (part.dim == adapt_cell.tdim) + { + const int n_cells = adapt_cell.n_entities(adapt_cell.tdim); + entities.reserve(static_cast(n_cells)); + for (int c = 0; c < n_cells; ++c) + { + auto verts = adapt_cell.entity_to_vertex[adapt_cell.tdim][ + static_cast(c)]; + if (!leaf_cell_matches_selection_expr( + adapt_cell, verts, part.expr, cut_active_mask, + *part.bg, parent_cell_id)) + { + continue; + } + + SelectedEntity entity; + entity.type = adapt_cell.entity_types[adapt_cell.tdim][ + static_cast(c)]; + entity.vertices.assign(verts.begin(), verts.end()); + entities.push_back(std::move(entity)); + } + return entities; + } + + const int n_zero = adapt_cell.n_zero_entities(); + entities.reserve(static_cast(n_zero)); + for (int z = 0; z < n_zero; ++z) + { + if (!zero_entity_matches_selection_expr( + adapt_cell, z, part.dim, part.expr, cut_active_mask, + *part.bg, parent_cell_id)) + { + continue; + } + + const int zdim = adapt_cell.zero_entity_dim[static_cast(z)]; + const int zid = adapt_cell.zero_entity_id[static_cast(z)]; + + SelectedEntity entity; + entity.local_zero_entity_id = z; + if (zdim == 0) + { + entity.type = cell::type::point; + entity.vertices.push_back(zid); + } + else + { + entity.type = adapt_cell.entity_types[zdim][static_cast(zid)]; + auto verts = adapt_cell.entity_to_vertex[zdim][static_cast(zid)]; + entity.vertices.assign(verts.begin(), verts.end()); + } + entities.push_back(std::move(entity)); + } + return entities; +} + +template +std::vector entity_reference_coords(const AdaptCell& adapt_cell, + std::span entity_vertices) +{ + std::vector coords( + static_cast(entity_vertices.size() * adapt_cell.tdim), T(0)); + + for (std::size_t j = 0; j < entity_vertices.size(); ++j) + { + const int gv = entity_vertices[j]; + for (int d = 0; d < adapt_cell.tdim; ++d) + { + coords[static_cast(j * adapt_cell.tdim + d)] = + adapt_cell.vertex_coords[static_cast( + gv * adapt_cell.tdim + d)]; + } + } + + return coords; +} + +template +void gather_subcell_vertices(std::span coords, + int coord_dim, + std::span vertex_ids, + std::vector& out) +{ + out.resize(static_cast(vertex_ids.size() * coord_dim)); + for (std::size_t j = 0; j < vertex_ids.size(); ++j) + { + const int local_v = vertex_ids[j]; + for (int d = 0; d < coord_dim; ++d) + { + out[static_cast(j * coord_dim + d)] = + coords[static_cast(local_v * coord_dim + d)]; + } + } +} + +template +void map_canonical_to_subcell_points(const T* canonical_points, + int num_points, + int simplex_dim, + const T* subcell_vertices, + int parent_tdim, + T* out_points) +{ + const T* v0 = subcell_vertices; + for (int q = 0; q < num_points; ++q) + { + const T* X = canonical_points + q * simplex_dim; + T* x = out_points + q * parent_tdim; + + for (int d = 0; d < parent_tdim; ++d) + x[d] = v0[d]; + + for (int i = 1; i <= simplex_dim; ++i) + { + const T* vi = subcell_vertices + i * parent_tdim; + for (int d = 0; d < parent_tdim; ++d) + x[d] += X[i - 1] * (vi[d] - v0[d]); + } + } +} + +template +T simplex_physical_measure(const T* vertices, + int simplex_dim, + int gdim) +{ + T J[9] = {}; + const T* v0 = vertices; + + for (int col = 0; col < simplex_dim; ++col) + { + const T* vi = vertices + (col + 1) * gdim; + for (int row = 0; row < gdim; ++row) + J[col * gdim + row] = vi[row] - v0[row]; + } + + if (simplex_dim == gdim) + { + if (simplex_dim == 1) + return std::abs(J[0]); + if (simplex_dim == 2) + return std::abs(J[0] * J[3] - J[2] * J[1]); + + const T det = + J[0] * (J[4] * J[8] - J[7] * J[5]) + - J[3] * (J[1] * J[8] - J[7] * J[2]) + + J[6] * (J[1] * J[5] - J[4] * J[2]); + return std::abs(det); + } + + T G[9] = {}; + for (int i = 0; i < simplex_dim; ++i) + { + for (int j = 0; j < simplex_dim; ++j) + { + T sum = 0; + for (int k = 0; k < gdim; ++k) + sum += J[i * gdim + k] * J[j * gdim + k]; + G[i * simplex_dim + j] = sum; + } + } + + if (simplex_dim == 1) + return std::sqrt(G[0]); + if (simplex_dim == 2) + return std::sqrt(G[0] * G[3] - G[1] * G[2]); + + const T det = + G[0] * (G[4] * G[8] - G[7] * G[5]) + - G[3] * (G[1] * G[8] - G[7] * G[2]) + + G[6] * (G[1] * G[5] - G[4] * G[2]); + return std::sqrt(det); +} + +template +void append_mesh_entity(mesh::CutMesh& out, + std::span physical_coords, + int gdim, + cell::type cell_type, + int parent_cell_id, + bool input_is_basix) +{ + if (out._gdim == 0) + out._gdim = gdim; + if (out._tdim == 0) + out._tdim = cell::get_tdim(cell_type); + + std::vector reordered_coords; + std::span output_coords = physical_coords; + if (!input_is_basix && !is_simplex(cell_type)) + { + const auto perm = cell::vtk_to_basix_vertex_permutation(cell_type); + reordered_coords = cell::permute_vertex_data(physical_coords, gdim, perm); + output_coords = std::span( + reordered_coords.data(), reordered_coords.size()); + } + + const int nv = static_cast(output_coords.size()) / gdim; + const int vertex_base = out._num_vertices; + out._vertex_coords.insert( + out._vertex_coords.end(), output_coords.begin(), output_coords.end()); + out._num_vertices += nv; + + for (int lv = 0; lv < nv; ++lv) + out._connectivity.push_back(vertex_base + lv); + out._offset.push_back(static_cast(out._connectivity.size())); + out._types.push_back(cell_type); + out._parent_map.push_back(parent_cell_id); + out._num_cells += 1; +} + +template +void append_simplex_quadrature(quadrature::QuadratureRules& rules, + cell::type simplex_type, + std::span ref_vertices, + std::span physical_vertices, + int parent_tdim, + int gdim, + int order) +{ + const auto ref_rule = quadrature::get_reference_rule(simplex_type, order); + const int num_points = ref_rule._num_points; + const int simplex_dim = ref_rule._tdim; + + std::vector mapped_ref_points( + static_cast(num_points * parent_tdim), T(0)); + map_canonical_to_subcell_points( + ref_rule._points.data(), + num_points, + simplex_dim, + ref_vertices.data(), + parent_tdim, + mapped_ref_points.data()); + + rules._points.insert( + rules._points.end(), mapped_ref_points.begin(), mapped_ref_points.end()); + + const T measure = simplex_physical_measure( + physical_vertices.data(), simplex_dim, gdim); + for (int q = 0; q < num_points; ++q) + rules._weights.push_back(ref_rule._weights[q] * measure); +} + +template +void append_entity_quadrature(quadrature::QuadratureRules& rules, + cell::type cell_type, + std::span ref_vertices, + std::span physical_vertices, + int parent_tdim, + int gdim, + int parent_cell_id, + int order) +{ + if (rules._tdim == 0) + rules._tdim = parent_tdim; + if (rules._offset.empty()) + rules._offset.push_back(0); + + if (cell_type == cell::type::point) + { + rules._points.insert( + rules._points.end(), + ref_vertices.begin(), + ref_vertices.begin() + parent_tdim); + rules._weights.push_back(T(1)); + rules._parent_map.push_back(parent_cell_id); + rules._offset.push_back(static_cast(rules._weights.size())); + return; + } + + const int entity_dim = cell::get_tdim(cell_type); + if (is_simplex(cell_type)) + { + append_simplex_quadrature( + rules, cell_type, ref_vertices, physical_vertices, + parent_tdim, gdim, order); + } + else if (entity_dim >= 2) + { + const auto ref_rule = quadrature::get_reference_rule(cell_type, order); + const T measure = cell::affine_volume_factor( + cell_type, physical_vertices.data(), gdim); + rules._points.insert( + rules._points.end(), ref_rule._points.begin(), ref_rule._points.end()); + for (int q = 0; q < ref_rule._num_points; ++q) + rules._weights.push_back(ref_rule._weights[q] * measure); + } + else + { + std::vector local_ids( + static_cast(ref_vertices.size() / parent_tdim)); + std::iota(local_ids.begin(), local_ids.end(), 0); + std::vector> simplices; + cell::triangulation(cell_type, local_ids.data(), simplices); + const auto simplex_type = simplex_type_for_dim(entity_dim); + + std::vector ref_simplex; + std::vector phys_simplex; + for (const auto& simplex : simplices) + { + gather_subcell_vertices( + ref_vertices, parent_tdim, + std::span(simplex.data(), simplex.size()), + ref_simplex); + gather_subcell_vertices( + physical_vertices, gdim, + std::span(simplex.data(), simplex.size()), + phys_simplex); + append_simplex_quadrature( + rules, + simplex_type, + std::span(ref_simplex.data(), ref_simplex.size()), + std::span(phys_simplex.data(), phys_simplex.size()), + parent_tdim, + gdim, + order); + } + } + + rules._parent_map.push_back(parent_cell_id); + rules._offset.push_back(static_cast(rules._weights.size())); +} + +template +void append_cut_entities(mesh::CutMesh& out, + quadrature::QuadratureRules* rules, + const HOMeshPart& part, + int quadrature_order) +{ + const auto& mesh = *part.bg->mesh; + for (std::int32_t cut_id : part.cut_cell_ids) + { + const auto& adapt_cell = + part.cut_cells->adapt_cells[static_cast(cut_id)]; + const auto entities = selected_entities(part, adapt_cell, cut_id); + if (entities.empty()) + continue; + + const I parent_cell_id = + part.cut_cells->parent_cell_ids[static_cast(cut_id)]; + const auto parent_vertex_coords = + parent_cell_vertex_coords_vtk(mesh, parent_cell_id); + + for (const auto& entity : entities) + { + const auto ref_coords = entity_reference_coords( + adapt_cell, + std::span(entity.vertices.data(), entity.vertices.size())); + const auto phys_coords = cell::push_forward_affine_map( + adapt_cell.parent_cell_type, + parent_vertex_coords, + mesh.gdim, + std::span(ref_coords.data(), ref_coords.size())); + + append_mesh_entity( + out, + std::span(phys_coords.data(), phys_coords.size()), + mesh.gdim, + entity.type, + static_cast(parent_cell_id), + /*input_is_basix=*/true); + + if (rules != nullptr) + { + append_entity_quadrature( + *rules, + entity.type, + std::span(ref_coords.data(), ref_coords.size()), + std::span(phys_coords.data(), phys_coords.size()), + mesh.tdim, + mesh.gdim, + static_cast(parent_cell_id), + quadrature_order); + } + } + } +} + +template +void append_uncut_volume_cells(mesh::CutMesh& out, + quadrature::QuadratureRules* rules, + const HOMeshPart& part, + int quadrature_order) +{ + const auto& mesh = *part.bg->mesh; + if (part.dim != mesh.tdim) + return; + + for (I cell_id : part.uncut_cell_ids) + { + const auto ctype = mesh.cell_type(cell_id); + const auto phys_coords = parent_cell_vertex_coords_vtk(mesh, cell_id); + append_mesh_entity( + out, + std::span(phys_coords.data(), phys_coords.size()), + mesh.gdim, + ctype, + static_cast(cell_id), + /*input_is_basix=*/false); + + if (rules != nullptr) + { + const auto ref_coords = cell::canonical_vertices(ctype); + append_entity_quadrature( + *rules, + ctype, + std::span(ref_coords.data(), ref_coords.size()), + std::span(phys_coords.data(), phys_coords.size()), + mesh.tdim, + mesh.gdim, + static_cast(cell_id), + quadrature_order); + } + } +} + +} // namespace + +template +std::vector selected_zero_entity_infos( + const HOMeshPart& part) +{ + if (!part.cut_cells || !part.bg) + throw std::runtime_error("HOMeshPart is not attached to cut-cell storage"); + + std::vector infos; + if (part.dim == part.cut_cells->tdim) + return infos; + + for (std::int32_t cut_id : part.cut_cell_ids) + { + const auto& ac = + part.cut_cells->adapt_cells[static_cast(cut_id)]; + const I parent_cell_id = + part.cut_cells->parent_cell_ids[static_cast(cut_id)]; + const std::uint64_t cut_active_mask = + part.cut_cells->active_level_set_mask[static_cast(cut_id)]; + + const int n_zero = ac.n_zero_entities(); + for (int z = 0; z < n_zero; ++z) + { + if (!zero_entity_matches_selection_expr( + ac, z, part.dim, part.expr, cut_active_mask, + *part.bg, parent_cell_id)) + { + continue; + } + + infos.push_back({ + cut_id, + static_cast(parent_cell_id), + static_cast(z), + static_cast(part.dim)}); + } + } + + return infos; +} + +template +mesh::CutMesh visualization_mesh(const HOMeshPart& part, + bool include_uncut_cells) +{ + if (!part.cut_cells || !part.bg || !part.bg->mesh) + throw std::runtime_error("HOMeshPart is not attached to cut-cell storage"); + + mesh::CutMesh out; + out._gdim = part.bg->mesh->gdim; + out._tdim = part.dim; + out._offset.push_back(0); + + append_cut_entities( + out, + static_cast*>(nullptr), + part, + /*quadrature_order=*/0); + if (include_uncut_cells) + { + append_uncut_volume_cells( + out, + static_cast*>(nullptr), + part, + /*quadrature_order=*/0); + } + + return out; +} + +template +quadrature::QuadratureRules quadrature_rules(const HOMeshPart& part, + int order, + bool include_uncut_cells) +{ + if (!part.cut_cells || !part.bg || !part.bg->mesh) + throw std::runtime_error("HOMeshPart is not attached to cut-cell storage"); + + mesh::CutMesh unused_mesh; + quadrature::QuadratureRules rules; + rules._offset.push_back(0); + + append_cut_entities(unused_mesh, &rules, part, order); + if (include_uncut_cells) + append_uncut_volume_cells(unused_mesh, &rules, part, order); + + return rules; +} + +template mesh::CutMesh visualization_mesh( + const HOMeshPart&, bool); +template mesh::CutMesh visualization_mesh( + const HOMeshPart&, bool); +template mesh::CutMesh visualization_mesh( + const HOMeshPart&, bool); +template mesh::CutMesh visualization_mesh( + const HOMeshPart&, bool); + +template std::vector selected_zero_entity_infos( + const HOMeshPart&); +template std::vector selected_zero_entity_infos( + const HOMeshPart&); +template std::vector selected_zero_entity_infos( + const HOMeshPart&); +template std::vector selected_zero_entity_infos( + const HOMeshPart&); + +template quadrature::QuadratureRules quadrature_rules( + const HOMeshPart&, int, bool); +template quadrature::QuadratureRules quadrature_rules( + const HOMeshPart&, int, bool); +template quadrature::QuadratureRules quadrature_rules( + const HOMeshPart&, int, bool); +template quadrature::QuadratureRules quadrature_rules( + const HOMeshPart&, int, bool); + +} // namespace cutcells::output diff --git a/cpp/src/ho_mesh_part_output.h b/cpp/src/ho_mesh_part_output.h new file mode 100644 index 0000000..13994a1 --- /dev/null +++ b/cpp/src/ho_mesh_part_output.h @@ -0,0 +1,40 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +#include "cut_mesh.h" +#include "ho_cut_mesh.h" +#include "quadrature.h" + +namespace cutcells::output +{ + +struct SelectedZeroEntityInfo +{ + std::int32_t cut_cell_id = -1; + std::int32_t parent_cell_id = -1; + std::int32_t local_zero_entity_id = -1; + std::int32_t dimension = -1; +}; + +template +std::vector selected_zero_entity_infos( + const HOMeshPart& part); + +template +mesh::CutMesh visualization_mesh(const HOMeshPart& part, + bool include_uncut_cells); + +template +quadrature::QuadratureRules quadrature_rules(const HOMeshPart& part, + int order, + bool include_uncut_cells); + +} // namespace cutcells::output diff --git a/cpp/src/level_set.cpp b/cpp/src/level_set.cpp new file mode 100644 index 0000000..40472cc --- /dev/null +++ b/cpp/src/level_set.cpp @@ -0,0 +1,1039 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "level_set.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cell_topology.h" +#include "cell_types.h" +#include "reference_cell.h" + +namespace cutcells +{ +namespace +{ + +template +struct EdgeKey +{ + I v0 = -1; + I v1 = -1; + + bool operator==(const EdgeKey&) const = default; +}; + +template +struct EdgeKeyHash +{ + std::size_t operator()(const EdgeKey& key) const noexcept + { + std::size_t seed = 0; + seed ^= std::hash{}(key.v0) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + seed ^= std::hash{}(key.v1) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template +struct FaceKey +{ + int nverts = 0; + std::array verts = {-1, -1, -1, -1}; + + bool operator==(const FaceKey&) const = default; +}; + +template +struct FaceKeyHash +{ + std::size_t operator()(const FaceKey& key) const noexcept + { + std::size_t seed = std::hash{}(key.nverts); + for (int i = 0; i < key.nverts; ++i) + seed ^= std::hash{}(key.verts[static_cast(i)]) + + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + return seed; + } +}; + +struct Param2Key +{ + int a = 0; + int b = 0; + + bool operator==(const Param2Key&) const = default; +}; + +struct Param2KeyHash +{ + std::size_t operator()(const Param2Key& key) const noexcept + { + std::size_t seed = 0; + seed ^= std::hash{}(key.a) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + seed ^= std::hash{}(key.b) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template +struct FaceData +{ + cell::type face_type = cell::type::point; + int nverts = 0; + std::array verts = {-1, -1, -1, -1}; + std::unordered_map dof_map; +}; + +template +I append_dof(LevelSetMeshData& out, std::span x, + int8_t parent_dim, int32_t parent_id, std::span params) +{ + const I dof = out.num_dofs(); + out.dof_coordinates.insert(out.dof_coordinates.end(), x.begin(), x.end()); + out.dof_parent_dim.push_back(parent_dim); + out.dof_parent_id.push_back(parent_id); + out.dof_parent_param.insert(out.dof_parent_param.end(), params.begin(), params.end()); + out.dof_parent_param_offset.push_back( + static_cast(out.dof_parent_param.size())); + return dof; +} + +template +void initialize_vertex_dofs(const MeshView& mesh, LevelSetMeshData& out) +{ + out.dof_coordinates.assign(mesh.coordinates.begin(), mesh.coordinates.end()); + const I ndofs = mesh.num_nodes(); + out.dof_parent_dim.assign(static_cast(ndofs), 0); + out.dof_parent_id.resize(static_cast(ndofs)); + for (I i = 0; i < ndofs; ++i) + out.dof_parent_id[static_cast(i)] = static_cast(i); + out.dof_parent_param_offset.assign(static_cast(ndofs) + 1, 0); +} + +template +cell::type infer_cell_type(const MeshView& mesh, I cell_id) +{ + if (mesh.has_cell_types()) + { + return mesh.cell_type(cell_id); + } + + const I n = mesh.cell_num_nodes(cell_id); + switch (mesh.tdim) + { + case 1: + if (n == 2) + return cell::type::interval; + break; + case 2: + if (n == 3) + return cell::type::triangle; + if (n == 4) + return cell::type::quadrilateral; + break; + case 3: + if (n == 4) + return cell::type::tetrahedron; + if (n == 5) + return cell::type::pyramid; + if (n == 6) + return cell::type::prism; + if (n == 8) + return cell::type::hexahedron; + break; + } + + throw std::runtime_error("create_level_set_mesh_data: unsupported cell type inference from MeshView"); +} + +template +std::vector cell_vertices_in_basix_order(const MeshView& mesh, I cell_id, + cell::type ctype) +{ + const std::span nodes = mesh.cell_nodes(cell_id); + const int nverts = cell::get_num_vertices(ctype); + if (static_cast(nodes.size()) != nverts) + { + throw std::runtime_error( + "create_level_set_mesh_data: MeshView cell connectivity must contain only corner vertices"); + } + + std::vector verts; + verts.reserve(static_cast(nverts)); + if (!mesh.vtk_vertex_order) + { + verts.insert(verts.end(), nodes.begin(), nodes.end()); + } + else + { + const auto perm = cell::vtk_to_basix_vertex_permutation(ctype); + for (int i = 0; i < nverts; ++i) + verts.push_back(nodes[static_cast(perm[static_cast(i)])]); + } + return verts; +} + +template +std::vector gather_vertex_coordinates(const MeshView& mesh, + const std::vector& vertex_ids) +{ + std::vector verts; + verts.reserve(static_cast(vertex_ids.size() * mesh.gdim)); + for (I v : vertex_ids) + { + const T* x = mesh.node(v); + verts.insert(verts.end(), x, x + mesh.gdim); + } + return verts; +} + +template +struct FaceDef +{ + cell::type face_type = cell::type::point; + int nverts = 0; + std::array verts = {-1, -1, -1, -1}; +}; + +inline std::span> basix_faces(cell::type ctype) +{ + static constexpr std::array, 4> tetrahedron = {{ + {cell::type::triangle, 3, {1, 2, 3, -1}}, + {cell::type::triangle, 3, {0, 2, 3, -1}}, + {cell::type::triangle, 3, {0, 1, 3, -1}}, + {cell::type::triangle, 3, {0, 1, 2, -1}}, + }}; + static constexpr std::array, 6> hexahedron = {{ + {cell::type::quadrilateral, 4, {0, 1, 2, 3}}, + {cell::type::quadrilateral, 4, {4, 5, 6, 7}}, + {cell::type::quadrilateral, 4, {0, 1, 4, 5}}, + {cell::type::quadrilateral, 4, {0, 2, 4, 6}}, + {cell::type::quadrilateral, 4, {1, 3, 5, 7}}, + {cell::type::quadrilateral, 4, {2, 3, 6, 7}}, + }}; + static constexpr std::array, 5> prism = {{ + {cell::type::triangle, 3, {0, 1, 2, -1}}, + {cell::type::triangle, 3, {3, 4, 5, -1}}, + {cell::type::quadrilateral, 4, {0, 1, 3, 4}}, + {cell::type::quadrilateral, 4, {0, 2, 3, 5}}, + {cell::type::quadrilateral, 4, {1, 2, 4, 5}}, + }}; + static constexpr std::array, 5> pyramid = {{ + {cell::type::quadrilateral, 4, {0, 1, 2, 3}}, + {cell::type::triangle, 3, {0, 1, 4, -1}}, + {cell::type::triangle, 3, {1, 2, 4, -1}}, + {cell::type::triangle, 3, {2, 3, 4, -1}}, + {cell::type::triangle, 3, {0, 3, 4, -1}}, + }}; + + switch (ctype) + { + case cell::type::tetrahedron: return std::span(tetrahedron); + case cell::type::hexahedron: return std::span(hexahedron); + case cell::type::prism: return std::span(prism); + case cell::type::pyramid: return std::span(pyramid); + default: return {}; + } +} + +template +std::array canonical_triangle(const std::array& v) +{ + std::array, 6> perms = {{ + {{v[0], v[1], v[2]}}, + {{v[0], v[2], v[1]}}, + {{v[1], v[0], v[2]}}, + {{v[1], v[2], v[0]}}, + {{v[2], v[0], v[1]}}, + {{v[2], v[1], v[0]}} + }}; + return *std::min_element(perms.begin(), perms.end()); +} + +template +std::array canonical_quad(const std::array& v) +{ + std::array, 8> perms = {{ + {{v[0], v[1], v[2], v[3]}}, + {{v[1], v[3], v[0], v[2]}}, + {{v[3], v[2], v[1], v[0]}}, + {{v[2], v[0], v[3], v[1]}}, + {{v[0], v[2], v[1], v[3]}}, + {{v[2], v[3], v[0], v[1]}}, + {{v[3], v[1], v[2], v[0]}}, + {{v[1], v[0], v[3], v[2]}} + }}; + return *std::min_element(perms.begin(), perms.end()); +} + +template +FaceKey canonical_face_key(cell::type face_type, const std::vector& local_vertices) +{ + FaceKey key; + key.nverts = static_cast(local_vertices.size()); + if (face_type == cell::type::triangle) + { + const std::array loc = { + local_vertices[0], local_vertices[1], local_vertices[2]}; + const std::array can = canonical_triangle(loc); + std::copy(can.begin(), can.end(), key.verts.begin()); + } + else if (face_type == cell::type::quadrilateral) + { + const std::array loc = { + local_vertices[0], local_vertices[1], local_vertices[2], local_vertices[3]}; + const std::array can = canonical_quad(loc); + std::copy(can.begin(), can.end(), key.verts.begin()); + } + else + { + throw std::runtime_error("create_level_set_mesh_data: unsupported face type"); + } + return key; +} + +template +std::vector local_to_canonical_positions(const std::vector& local_vertices, + const std::array& canonical_vertices, + int nverts) +{ + std::vector pos(static_cast(nverts), -1); + for (int p = 0; p < nverts; ++p) + { + for (int q = 0; q < nverts; ++q) + { + if (local_vertices[static_cast(p)] == canonical_vertices[static_cast(q)]) + { + pos[static_cast(p)] = q; + break; + } + } + if (pos[static_cast(p)] < 0) + throw std::runtime_error("create_level_set_mesh_data: invalid face permutation"); + } + return pos; +} + +template +void affine_edge_point(std::span x0, std::span x1, T t, std::vector& out) +{ + out.resize(x0.size()); + for (std::size_t d = 0; d < x0.size(); ++d) + out[d] = (T(1) - t) * x0[d] + t * x1[d]; +} + +template +void bary_triangle_point(std::span x0, std::span x1, std::span x2, + T u, T v, std::vector& out) +{ + const T w0 = T(1) - u - v; + out.resize(x0.size()); + for (std::size_t d = 0; d < x0.size(); ++d) + out[d] = w0 * x0[d] + u * x1[d] + v * x2[d]; +} + +template +void bilinear_quad_point(std::span x0, std::span x1, + std::span x2, std::span x3, + T u, T v, std::vector& out) +{ + const T w0 = (T(1) - u) * (T(1) - v); + const T w1 = u * (T(1) - v); + const T w2 = (T(1) - u) * v; + const T w3 = u * v; + out.resize(x0.size()); + for (std::size_t d = 0; d < x0.size(); ++d) + out[d] = w0 * x0[d] + w1 * x1[d] + w2 * x2[d] + w3 * x3[d]; +} + +template +void bary_tetra_point(std::span x0, std::span x1, + std::span x2, std::span x3, + T u, T v, T w, std::vector& out) +{ + const T w0 = T(1) - u - v - w; + out.resize(x0.size()); + for (std::size_t d = 0; d < x0.size(); ++d) + out[d] = w0 * x0[d] + u * x1[d] + v * x2[d] + w * x3[d]; +} + +template +void multilinear_hex_point(const std::vector& verts, int gdim, + T u, T v, T w, std::vector& out) +{ + out.resize(static_cast(gdim)); + for (int d = 0; d < gdim; ++d) + { + out[static_cast(d)] = + (T(1) - u) * (T(1) - v) * (T(1) - w) * verts[static_cast(d)] + + u * (T(1) - v) * (T(1) - w) * verts[static_cast(gdim + d)] + + (T(1) - u) * v * (T(1) - w) * verts[static_cast(2 * gdim + d)] + + u * v * (T(1) - w) * verts[static_cast(3 * gdim + d)] + + (T(1) - u) * (T(1) - v) * w * verts[static_cast(4 * gdim + d)] + + u * (T(1) - v) * w * verts[static_cast(5 * gdim + d)] + + (T(1) - u) * v * w * verts[static_cast(6 * gdim + d)] + + u * v * w * verts[static_cast(7 * gdim + d)]; + } +} + +template +void multilinear_prism_point(const std::vector& verts, int gdim, + T u, T v, T w, std::vector& out) +{ + const T l0 = T(1) - u - v; + const T l1 = u; + const T l2 = v; + out.resize(static_cast(gdim)); + for (int d = 0; d < gdim; ++d) + { + out[static_cast(d)] = + l0 * (T(1) - w) * verts[static_cast(d)] + + l1 * (T(1) - w) * verts[static_cast(gdim + d)] + + l2 * (T(1) - w) * verts[static_cast(2 * gdim + d)] + + l0 * w * verts[static_cast(3 * gdim + d)] + + l1 * w * verts[static_cast(4 * gdim + d)] + + l2 * w * verts[static_cast(5 * gdim + d)]; + } +} + +template +EdgeKey make_edge_key(I a, I b) +{ + return (a <= b) ? EdgeKey{a, b} : EdgeKey{b, a}; +} + +template +bool edge_matches_canonical(I a, I b, const EdgeKey& key) +{ + return a == key.v0 && b == key.v1; +} + +inline std::array transform_quad_indices(int i, int j, int p, + const std::vector& local_to_can) +{ + static constexpr std::array, 8> perms = {{ + {{0, 1, 2, 3}}, + {{1, 3, 0, 2}}, + {{3, 2, 1, 0}}, + {{2, 0, 3, 1}}, + {{0, 2, 1, 3}}, + {{2, 3, 0, 1}}, + {{3, 1, 2, 0}}, + {{1, 0, 3, 2}}, + }}; + + for (const auto& perm : perms) + { + bool match = true; + for (int k = 0; k < 4; ++k) + { + if (local_to_can[static_cast(k)] != perm[static_cast(k)]) + { + match = false; + break; + } + } + if (!match) + continue; + + switch (&perm - &perms[0]) + { + case 0: return {i, j, 0, 0}; + case 1: return {j, p - i, 0, 0}; + case 2: return {p - i, p - j, 0, 0}; + case 3: return {p - j, i, 0, 0}; + case 4: return {j, i, 0, 0}; + case 5: return {p - i, j, 0, 0}; + case 6: return {p - j, p - i, 0, 0}; + case 7: return {i, p - j, 0, 0}; + } + } + + throw std::runtime_error("create_level_set_mesh_data: unsupported quad face permutation"); +} + +template +I get_or_create_edge_block(const MeshView& mesh, + LevelSetMeshData& out, + std::unordered_map, I, EdgeKeyHash>& edge_ids, + std::vector& edge_first_dof, + I a, I b, int degree) +{ + const EdgeKey key = make_edge_key(a, b); + auto it = edge_ids.find(key); + if (it != edge_ids.end()) + return it->second; + + const I edge_id = static_cast(edge_ids.size()); + edge_ids.emplace(key, edge_id); + edge_first_dof.push_back(static_cast(-1)); + if (degree == 1) + return edge_id; + + edge_first_dof[static_cast(edge_id)] = out.num_dofs(); + std::vector x; + std::array param; + for (int k = 1; k < degree; ++k) + { + const T t = static_cast(k) / static_cast(degree); + affine_edge_point( + std::span(mesh.node(key.v0), static_cast(mesh.gdim)), + std::span(mesh.node(key.v1), static_cast(mesh.gdim)), + t, x); + param[0] = t; + append_dof(out, std::span(x.data(), x.size()), 1, + static_cast(edge_id), + std::span(param.data(), param.size())); + } + return edge_id; +} + +template +I get_or_create_face_block(const MeshView& mesh, + LevelSetMeshData& out, + std::unordered_map, I, FaceKeyHash>& face_ids, + std::vector>& faces, + cell::type face_type, + const std::vector& local_vertices, + int degree) +{ + const FaceKey key = canonical_face_key(face_type, local_vertices); + auto it = face_ids.find(key); + if (it != face_ids.end()) + return it->second; + + const I face_id = static_cast(faces.size()); + face_ids.emplace(key, face_id); + + FaceData face; + face.face_type = face_type; + face.nverts = key.nverts; + face.verts = key.verts; + + if (degree > 2 || face_type == cell::type::quadrilateral) + { + std::vector x; + if (face_type == cell::type::triangle) + { + std::array param; + const T* x0 = mesh.node(face.verts[0]); + const T* x1 = mesh.node(face.verts[1]); + const T* x2 = mesh.node(face.verts[2]); + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + bary_triangle_point( + std::span(x0, static_cast(mesh.gdim)), + std::span(x1, static_cast(mesh.gdim)), + std::span(x2, static_cast(mesh.gdim)), + u, v, x); + param[0] = u; + param[1] = v; + const I dof = append_dof(out, std::span(x.data(), x.size()), 2, + static_cast(face_id), + std::span(param.data(), param.size())); + face.dof_map.emplace(Param2Key{i, j}, dof); + } + } + } + else + { + std::array param; + const T* x0 = mesh.node(face.verts[0]); + const T* x1 = mesh.node(face.verts[1]); + const T* x2 = mesh.node(face.verts[2]); + const T* x3 = mesh.node(face.verts[3]); + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + bilinear_quad_point( + std::span(x0, static_cast(mesh.gdim)), + std::span(x1, static_cast(mesh.gdim)), + std::span(x2, static_cast(mesh.gdim)), + std::span(x3, static_cast(mesh.gdim)), + u, v, x); + param[0] = u; + param[1] = v; + const I dof = append_dof(out, std::span(x.data(), x.size()), 2, + static_cast(face_id), + std::span(param.data(), param.size())); + face.dof_map.emplace(Param2Key{i, j}, dof); + } + } + } + } + + faces.push_back(std::move(face)); + return face_id; +} + +template +void append_interval_cell_dofs(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + out.cell_dofs.push_back(verts[0]); + out.cell_dofs.push_back(verts[1]); + if (degree == 1) + return; + + std::vector x; + std::array param; + for (int k = 1; k < degree; ++k) + { + const T t = static_cast(k) / static_cast(degree); + affine_edge_point( + std::span(mesh.node(verts[0]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[1]), static_cast(mesh.gdim)), + t, x); + param[0] = t; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 1, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } +} + +template +void append_triangle_cell_interior(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + if (degree <= 2) + return; + + std::vector x; + std::array param; + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + bary_triangle_point( + std::span(mesh.node(verts[0]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[1]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[2]), static_cast(mesh.gdim)), + u, v, x); + param[0] = u; + param[1] = v; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 2, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } + } +} + +template +void append_quadrilateral_cell_interior(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + if (degree <= 1) + return; + + std::vector x; + std::array param; + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + bilinear_quad_point( + std::span(mesh.node(verts[0]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[1]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[2]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[3]), static_cast(mesh.gdim)), + u, v, x); + param[0] = u; + param[1] = v; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 2, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } + } +} + +template +void append_tetrahedron_cell_interior(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + if (degree <= 3) + return; + + std::vector x; + std::array param; + for (int k = 1; k <= degree - 3; ++k) + { + for (int j = 1; j <= degree - k - 2; ++j) + { + for (int i = 1; i <= degree - j - k - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + const T w = static_cast(k) / static_cast(degree); + bary_tetra_point( + std::span(mesh.node(verts[0]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[1]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[2]), static_cast(mesh.gdim)), + std::span(mesh.node(verts[3]), static_cast(mesh.gdim)), + u, v, w, x); + param[0] = u; + param[1] = v; + param[2] = w; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 3, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } + } + } +} + +template +void append_hexahedron_cell_interior(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + if (degree <= 1) + return; + + const std::vector xverts = gather_vertex_coordinates(mesh, verts); + std::vector x; + std::array param; + for (int k = 1; k < degree; ++k) + { + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + const T w = static_cast(k) / static_cast(degree); + multilinear_hex_point(xverts, mesh.gdim, u, v, w, x); + param[0] = u; + param[1] = v; + param[2] = w; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 3, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } + } + } +} + +template +void append_prism_cell_interior(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, const std::vector& verts, int degree) +{ + if (degree <= 2) + return; + + const std::vector xverts = gather_vertex_coordinates(mesh, verts); + std::vector x; + std::array param; + for (int k = 1; k < degree; ++k) + { + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + const T w = static_cast(k) / static_cast(degree); + multilinear_prism_point(xverts, mesh.gdim, u, v, w, x); + param[0] = u; + param[1] = v; + param[2] = w; + out.cell_dofs.push_back(append_dof( + out, std::span(x.data(), x.size()), 3, + static_cast(cell_id), + std::span(param.data(), param.size()))); + } + } + } +} + +template +void append_cell_dofs(const MeshView& mesh, LevelSetMeshData& out, + I cell_id, cell::type ctype, const std::vector& verts, + int degree, + std::unordered_map, I, EdgeKeyHash>& edge_ids, + std::vector& edge_first_dof, + std::unordered_map, I, FaceKeyHash>& face_ids, + std::vector>& faces) +{ + for (I v : verts) + out.cell_dofs.push_back(v); + + if (ctype == cell::type::interval) + { + append_interval_cell_dofs(mesh, out, cell_id, verts, degree); + return; + } + + const auto edges = cell::edges(ctype); + for (std::size_t e = 0; e < edges.size(); ++e) + { + const I a = verts[static_cast(edges[e][0])]; + const I b = verts[static_cast(edges[e][1])]; + const EdgeKey key = make_edge_key(a, b); + const bool same_orientation = edge_matches_canonical(a, b, key); + const I edge_id = get_or_create_edge_block( + mesh, out, edge_ids, edge_first_dof, a, b, degree); + if (degree == 1) + continue; + + const I first = edge_first_dof[static_cast(edge_id)]; + for (int k = 1; k < degree; ++k) + { + const int canonical_k = same_orientation ? k : (degree - k); + out.cell_dofs.push_back(first + static_cast(canonical_k - 1)); + } + } + + if (mesh.tdim == 3) + { + const auto faces_local = basix_faces(ctype); + for (std::size_t f = 0; f < faces_local.size(); ++f) + { + const auto& fdef = faces_local[f]; + std::vector fverts; + fverts.reserve(static_cast(fdef.nverts)); + for (int j = 0; j < fdef.nverts; ++j) + fverts.push_back(verts[static_cast(fdef.verts[static_cast(j)])]); + + const I face_id = get_or_create_face_block( + mesh, out, face_ids, faces, fdef.face_type, fverts, degree); + const FaceData& face = faces[static_cast(face_id)]; + const std::vector local_to_can = + local_to_canonical_positions(fverts, face.verts, fdef.nverts); + + if (fdef.face_type == cell::type::triangle) + { + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + int local_bary[3] = {degree - i - j, i, j}; + int can_bary[3] = {0, 0, 0}; + for (int p = 0; p < 3; ++p) + can_bary[local_to_can[static_cast(p)]] = local_bary[p]; + + const auto it = face.dof_map.find(Param2Key{can_bary[1], can_bary[2]}); + if (it == face.dof_map.end()) + throw std::runtime_error("create_level_set_mesh_data: missing triangle face dof"); + out.cell_dofs.push_back(it->second); + } + } + } + else + { + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const auto can = transform_quad_indices(i, j, degree, local_to_can); + const auto it = face.dof_map.find(Param2Key{can[0], can[1]}); + if (it == face.dof_map.end()) + throw std::runtime_error("create_level_set_mesh_data: missing quadrilateral face dof"); + out.cell_dofs.push_back(it->second); + } + } + } + } + } + + switch (ctype) + { + case cell::type::triangle: + append_triangle_cell_interior(mesh, out, cell_id, verts, degree); + break; + case cell::type::quadrilateral: + append_quadrilateral_cell_interior(mesh, out, cell_id, verts, degree); + break; + case cell::type::tetrahedron: + append_tetrahedron_cell_interior(mesh, out, cell_id, verts, degree); + break; + case cell::type::hexahedron: + append_hexahedron_cell_interior(mesh, out, cell_id, verts, degree); + break; + case cell::type::prism: + append_prism_cell_interior(mesh, out, cell_id, verts, degree); + break; + case cell::type::pyramid: + if (degree > 1) + { + throw std::runtime_error( + "create_level_set_mesh_data: pyramid degree > 1 is not implemented yet"); + } + break; + default: + break; + } +} + +template +void validate_mesh_data_args(int gdim, int tdim, int degree, + std::span dof_coordinates, + std::span cell_dofs, + std::span cell_offsets, + std::span cell_types) +{ + if (gdim <= 0) + throw std::runtime_error("create_level_set_mesh_data: gdim must be positive"); + if (tdim <= 0) + throw std::runtime_error("create_level_set_mesh_data: tdim must be positive"); + if (degree < 1) + throw std::runtime_error("create_level_set_mesh_data: degree must be >= 1"); + if (dof_coordinates.size() % static_cast(gdim) != 0) + throw std::runtime_error("create_level_set_mesh_data: dof_coordinates size must be divisible by gdim"); + if (cell_offsets.empty()) + throw std::runtime_error("create_level_set_mesh_data: cell_offsets must not be empty"); + if (cell_offsets.front() != 0) + throw std::runtime_error("create_level_set_mesh_data: cell_offsets must start at 0"); + if (static_cast(cell_offsets.back()) != cell_dofs.size()) + throw std::runtime_error("create_level_set_mesh_data: cell_offsets.back() must equal cell_dofs.size()"); + if (!cell_types.empty() && cell_types.size() + 1 != cell_offsets.size()) + throw std::runtime_error("create_level_set_mesh_data: cell_types size must equal number of cells"); +} + +} // namespace + +template +LevelSetMeshData create_level_set_mesh_data( + const MeshView& mesh, int degree, T merge_tol) +{ + (void)merge_tol; + if (degree < 1) + throw std::runtime_error("create_level_set_mesh_data: degree must be >= 1"); + if (mesh.gdim <= 0 || mesh.tdim <= 0) + throw std::runtime_error("create_level_set_mesh_data: MeshView dimensions must be positive"); + + LevelSetMeshData out; + out.gdim = mesh.gdim; + out.tdim = mesh.tdim; + out.degree = degree; + out.cell_offsets.reserve(static_cast(mesh.num_cells()) + 1); + out.cell_types.reserve(static_cast(mesh.num_cells())); + out.cell_offsets.push_back(0); + + initialize_vertex_dofs(mesh, out); + + std::unordered_map, I, EdgeKeyHash> edge_ids; + std::vector edge_first_dof; + std::unordered_map, I, FaceKeyHash> face_ids; + std::vector> faces; + + for (I cell_id = 0; cell_id < mesh.num_cells(); ++cell_id) + { + const cell::type ctype = infer_cell_type(mesh, cell_id); + const std::vector verts = cell_vertices_in_basix_order(mesh, cell_id, ctype); + append_cell_dofs(mesh, out, cell_id, ctype, verts, degree, + edge_ids, edge_first_dof, face_ids, faces); + + out.cell_types.push_back(ctype); + out.cell_offsets.push_back(static_cast(out.cell_dofs.size())); + } + + return out; +} + +template +LevelSetMeshData create_level_set_mesh_data( + int gdim, + int tdim, + int degree, + std::span dof_coordinates, + std::span cell_dofs, + std::span cell_offsets, + std::span cell_types) +{ + validate_mesh_data_args(gdim, tdim, degree, + dof_coordinates, cell_dofs, cell_offsets, cell_types); + + LevelSetMeshData out; + out.gdim = gdim; + out.tdim = tdim; + out.degree = degree; + out.dof_coordinates.assign(dof_coordinates.begin(), dof_coordinates.end()); + out.cell_dofs.assign(cell_dofs.begin(), cell_dofs.end()); + out.cell_offsets.assign(cell_offsets.begin(), cell_offsets.end()); + out.cell_types.assign(cell_types.begin(), cell_types.end()); + return out; +} + +template +LevelSetFunction create_level_set_function( + std::shared_ptr> mesh_data, + std::span dof_values, + std::string name) +{ + if (!mesh_data) + throw std::runtime_error("create_level_set_function: mesh_data must not be null"); + + const std::size_t expected = static_cast(mesh_data->num_dofs()); + if (dof_values.size() != expected) + { + throw std::runtime_error( + "create_level_set_function: dof_values size mismatch (got " + + std::to_string(dof_values.size()) + ", expected " + + std::to_string(expected) + ")"); + } + + auto owned_values = std::make_shared>(dof_values.begin(), dof_values.end()); + + LevelSetFunction ls; + ls.name = std::move(name); + ls.type = LevelSetType::Polynomial; + ls.gdim = mesh_data->gdim; + ls.mesh_data = std::move(mesh_data); + ls.dof_values = std::span(owned_values->data(), owned_values->size()); + ls.owner = std::static_pointer_cast(owned_values); + return ls; +} + +template LevelSetMeshData create_level_set_mesh_data( + const MeshView& mesh, int degree, float merge_tol); +template LevelSetMeshData create_level_set_mesh_data( + const MeshView& mesh, int degree, double merge_tol); + +template LevelSetMeshData create_level_set_mesh_data( + int gdim, int tdim, int degree, + std::span dof_coordinates, + std::span cell_dofs, + std::span cell_offsets, + std::span cell_types); +template LevelSetMeshData create_level_set_mesh_data( + int gdim, int tdim, int degree, + std::span dof_coordinates, + std::span cell_dofs, + std::span cell_offsets, + std::span cell_types); + +template LevelSetFunction create_level_set_function( + std::shared_ptr> mesh_data, + std::span dof_values, + std::string name); +template LevelSetFunction create_level_set_function( + std::shared_ptr> mesh_data, + std::span dof_values, + std::string name); + +} // namespace cutcells diff --git a/cpp/src/level_set.h b/cpp/src/level_set.h new file mode 100644 index 0000000..6e40c34 --- /dev/null +++ b/cpp/src/level_set.h @@ -0,0 +1,190 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "cell_types.h" +#include "mesh_view.h" + +namespace cutcells +{ + +enum class LevelSetType +{ + Analytical, + Polynomial +}; + +template +struct LevelSetMeshData +{ + int gdim = 0; + int tdim = 0; + int degree = 1; + + // Flattened coordinates of the discrete level-set dofs: + // [x0_0, x0_1, ..., x1_0, x1_1, ...] + std::vector dof_coordinates; + + // CSR-like cell -> global dof map. The local ordering on each cell follows + // Basix ordering for the corresponding cell type and degree. + std::vector cell_dofs; + std::vector cell_offsets; + + // cell types, one per cell when available. + std::vector cell_types; + + // Provenance aligned with AdaptCell: + // 0 = parent vertex, 1 = parent edge, 2 = parent face, 3 = parent cell interior. + std::vector dof_parent_dim; + std::vector dof_parent_id; + std::vector dof_parent_param; + std::vector dof_parent_param_offset; + + I num_dofs() const + { + if (gdim <= 0) + return 0; + return static_cast(dof_coordinates.size() / static_cast(gdim)); + } + + I num_cells() const + { + if (cell_offsets.empty()) + return 0; + return static_cast(cell_offsets.size() - 1); + } + + I cell_num_dofs(I cell_id) const + { + return cell_offsets[static_cast(cell_id) + 1] + - cell_offsets[static_cast(cell_id)]; + } + + std::span cell_dofs_span(I cell_id) const + { + const std::size_t begin = static_cast( + cell_offsets[static_cast(cell_id)]); + const std::size_t end = static_cast( + cell_offsets[static_cast(cell_id) + 1]); + return std::span(cell_dofs.data() + begin, end - begin); + } + + const T* dof_coordinate(I dof_id) const + { + return dof_coordinates.data() + + static_cast(dof_id) * static_cast(gdim); + } + + std::span dof_parent_param_span(I dof_id) const + { + const std::size_t begin = static_cast( + dof_parent_param_offset[static_cast(dof_id)]); + const std::size_t end = static_cast( + dof_parent_param_offset[static_cast(dof_id) + 1]); + return std::span(dof_parent_param.data() + begin, end - begin); + } +}; + +template +struct LevelSetFunction +{ + + std::string name = "phi"; + LevelSetType type = LevelSetType::Analytical; + int gdim = 0; + + // cell_id == -1 means "unknown / not provided" + // The value and gradient of the level set function in physical coordinates (x) + // together with background cell id. + std::function value_fn; + std::function grad_fn; + + // Legacy low-order nodal storage on mesh vertices. + std::span nodal_values; + + // Optional higher-order mesh/dof layout and corresponding global dof values. + std::shared_ptr> mesh_data; + std::span dof_values; + + // Optional keep-alive anchor for Python / external memory. + std::shared_ptr owner; + + bool has_value() const + { + return static_cast(value_fn); + } + + bool has_gradient() const + { + return static_cast(grad_fn); + } + + bool has_nodal_values() const + { + return !nodal_values.empty(); + } + + bool has_mesh_data() const + { + return static_cast(mesh_data); + } + + bool has_dof_values() const + { + return !dof_values.empty(); + } + + T value(const T* x, I cell_id = static_cast(-1)) const + { + if (!value_fn) + throw std::runtime_error("LevelSetFunction::value not available"); + return value_fn(x, cell_id); + } + + void grad(const T* x, I cell_id, T* g) const + { + if (!grad_fn) + throw std::runtime_error("LevelSetFunction::grad not available"); + grad_fn(x, cell_id, g); + } + + T value_at_node(I node_id) const + { + if (nodal_values.empty()) + throw std::runtime_error("LevelSetFunction::value_at_node not available"); + return nodal_values[static_cast(node_id)]; + } +}; + +template +LevelSetMeshData create_level_set_mesh_data( + const MeshView& mesh, int degree, T merge_tol = T(-1)); + +template +LevelSetMeshData create_level_set_mesh_data( + int gdim, + int tdim, + int degree, + std::span dof_coordinates, + std::span cell_dofs, + std::span cell_offsets, + std::span cell_types = {}); + +template +LevelSetFunction create_level_set_function( + std::shared_ptr> mesh_data, + std::span dof_values, + std::string name = "phi"); + +} // namespace cutcells diff --git a/cpp/src/level_set_cell.cpp b/cpp/src/level_set_cell.cpp new file mode 100644 index 0000000..a67a83b --- /dev/null +++ b/cpp/src/level_set_cell.cpp @@ -0,0 +1,625 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "level_set_cell.h" + +#include +#include +#include + +#include "bernstein.h" +#include "cell_topology.h" +#include "mapping.h" +#include "reference_cell.h" + +namespace cutcells +{ +namespace +{ + +template +struct RefPointKey +{ + cell::type ctype = cell::type::point; + int degree = 0; + + bool operator==(const RefPointKey&) const = default; +}; + +template +struct RefPointKeyHash +{ + std::size_t operator()(const RefPointKey& key) const noexcept + { + std::size_t seed = std::hash{}(static_cast(key.ctype)); + seed ^= std::hash{}(key.degree) + 0x9e3779b97f4a7c15ULL + + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template +struct FaceDef +{ + cell::type face_type = cell::type::point; + int nverts = 0; + std::array verts = {-1, -1, -1, -1}; +}; + +inline std::span> basix_faces(cell::type ctype) +{ + static constexpr std::array, 4> tetrahedron = {{ + {cell::type::triangle, 3, {1, 2, 3, -1}}, + {cell::type::triangle, 3, {0, 2, 3, -1}}, + {cell::type::triangle, 3, {0, 1, 3, -1}}, + {cell::type::triangle, 3, {0, 1, 2, -1}}, + }}; + static constexpr std::array, 6> hexahedron = {{ + {cell::type::quadrilateral, 4, {0, 1, 2, 3}}, + {cell::type::quadrilateral, 4, {4, 5, 6, 7}}, + {cell::type::quadrilateral, 4, {0, 1, 4, 5}}, + {cell::type::quadrilateral, 4, {0, 2, 4, 6}}, + {cell::type::quadrilateral, 4, {1, 3, 5, 7}}, + {cell::type::quadrilateral, 4, {2, 3, 6, 7}}, + }}; + static constexpr std::array, 5> prism = {{ + {cell::type::triangle, 3, {0, 1, 2, -1}}, + {cell::type::triangle, 3, {3, 4, 5, -1}}, + {cell::type::quadrilateral, 4, {0, 1, 3, 4}}, + {cell::type::quadrilateral, 4, {0, 2, 3, 5}}, + {cell::type::quadrilateral, 4, {1, 2, 4, 5}}, + }}; + static constexpr std::array, 5> pyramid = {{ + {cell::type::quadrilateral, 4, {0, 1, 2, 3}}, + {cell::type::triangle, 3, {0, 1, 4, -1}}, + {cell::type::triangle, 3, {1, 2, 4, -1}}, + {cell::type::triangle, 3, {2, 3, 4, -1}}, + {cell::type::triangle, 3, {0, 3, 4, -1}}, + }}; + + switch (ctype) + { + case cell::type::tetrahedron: return std::span(tetrahedron); + case cell::type::hexahedron: return std::span(hexahedron); + case cell::type::prism: return std::span(prism); + case cell::type::pyramid: return std::span(pyramid); + default: return {}; + } +} + +template +void append_point(std::vector& out, std::span x) +{ + out.insert(out.end(), x.begin(), x.end()); +} + +template +void append_affine_edge_point(std::vector& out, + std::span x0, + std::span x1, + T t) +{ + for (std::size_t d = 0; d < x0.size(); ++d) + out.push_back((T(1) - t) * x0[d] + t * x1[d]); +} + +template +void append_triangle_face_point(std::vector& out, + std::span x0, + std::span x1, + std::span x2, + T u, T v) +{ + const T w0 = T(1) - u - v; + for (std::size_t d = 0; d < x0.size(); ++d) + out.push_back(w0 * x0[d] + u * x1[d] + v * x2[d]); +} + +template +void append_quad_face_point(std::vector& out, + std::span x0, + std::span x1, + std::span x2, + std::span x3, + T u, T v) +{ + const T w0 = (T(1) - u) * (T(1) - v); + const T w1 = u * (T(1) - v); + const T w2 = (T(1) - u) * v; + const T w3 = u * v; + for (std::size_t d = 0; d < x0.size(); ++d) + out.push_back(w0 * x0[d] + w1 * x1[d] + w2 * x2[d] + w3 * x3[d]); +} + +template +void append_tetra_cell_point(std::vector& out, + std::span x0, + std::span x1, + std::span x2, + std::span x3, + T u, T v, T w) +{ + const T w0 = T(1) - u - v - w; + for (std::size_t d = 0; d < x0.size(); ++d) + out.push_back(w0 * x0[d] + u * x1[d] + v * x2[d] + w * x3[d]); +} + +template +void append_hex_cell_point(std::vector& out, + const std::vector& verts, int tdim, + T u, T v, T w) +{ + for (int d = 0; d < tdim; ++d) + { + out.push_back( + (T(1) - u) * (T(1) - v) * (T(1) - w) * verts[static_cast(d)] + + u * (T(1) - v) * (T(1) - w) * verts[static_cast(tdim + d)] + + (T(1) - u) * v * (T(1) - w) * verts[static_cast(2 * tdim + d)] + + u * v * (T(1) - w) * verts[static_cast(3 * tdim + d)] + + (T(1) - u) * (T(1) - v) * w * verts[static_cast(4 * tdim + d)] + + u * (T(1) - v) * w * verts[static_cast(5 * tdim + d)] + + (T(1) - u) * v * w * verts[static_cast(6 * tdim + d)] + + u * v * w * verts[static_cast(7 * tdim + d)]); + } +} + +template +std::vector build_reference_lagrange_points(cell::type ctype, int degree) +{ + const int tdim = cell::get_tdim(ctype); + const int nv = cell::get_num_vertices(ctype); + const std::vector vertices = cell::reference_vertices(ctype); + std::vector ref_points; + ref_points.reserve(static_cast( + bernstein::num_polynomials(ctype, degree) * tdim)); + + for (int v = 0; v < nv; ++v) + { + append_point( + ref_points, + std::span( + vertices.data() + static_cast(v * tdim), + static_cast(tdim))); + } + + if (degree == 1) + return ref_points; + + const auto edges = cell::edges(ctype); + for (const auto& edge : edges) + { + const T* x0 = vertices.data() + static_cast(edge[0] * tdim); + const T* x1 = vertices.data() + static_cast(edge[1] * tdim); + for (int k = 1; k < degree; ++k) + { + const T t = static_cast(k) / static_cast(degree); + append_affine_edge_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + t); + } + } + + if (tdim == 3) + { + const auto faces = basix_faces(ctype); + for (const auto& face : faces) + { + if (face.face_type == cell::type::triangle) + { + if (degree <= 2) + continue; + + const T* x0 = vertices.data() + static_cast(face.verts[0] * tdim); + const T* x1 = vertices.data() + static_cast(face.verts[1] * tdim); + const T* x2 = vertices.data() + static_cast(face.verts[2] * tdim); + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + append_triangle_face_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + std::span(x2, static_cast(tdim)), + u, v); + } + } + } + else if (face.face_type == cell::type::quadrilateral) + { + const T* x0 = vertices.data() + static_cast(face.verts[0] * tdim); + const T* x1 = vertices.data() + static_cast(face.verts[1] * tdim); + const T* x2 = vertices.data() + static_cast(face.verts[2] * tdim); + const T* x3 = vertices.data() + static_cast(face.verts[3] * tdim); + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + append_quad_face_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + std::span(x2, static_cast(tdim)), + std::span(x3, static_cast(tdim)), + u, v); + } + } + } + } + } + + switch (ctype) + { + case cell::type::triangle: + if (degree > 2) + { + const T* x0 = vertices.data(); + const T* x1 = vertices.data() + tdim; + const T* x2 = vertices.data() + 2 * tdim; + for (int j = 1; j <= degree - 2; ++j) + { + for (int i = 1; i <= degree - j - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + append_triangle_face_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + std::span(x2, static_cast(tdim)), + u, v); + } + } + } + break; + case cell::type::quadrilateral: + { + const T* x0 = vertices.data(); + const T* x1 = vertices.data() + tdim; + const T* x2 = vertices.data() + 2 * tdim; + const T* x3 = vertices.data() + 3 * tdim; + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + append_quad_face_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + std::span(x2, static_cast(tdim)), + std::span(x3, static_cast(tdim)), + u, v); + } + } + } + break; + case cell::type::tetrahedron: + if (degree > 3) + { + const T* x0 = vertices.data(); + const T* x1 = vertices.data() + tdim; + const T* x2 = vertices.data() + 2 * tdim; + const T* x3 = vertices.data() + 3 * tdim; + for (int k = 1; k <= degree - 3; ++k) + { + for (int j = 1; j <= degree - k - 2; ++j) + { + for (int i = 1; i <= degree - j - k - 1; ++i) + { + const T u = static_cast(i) / static_cast(degree); + const T v = static_cast(j) / static_cast(degree); + const T w = static_cast(k) / static_cast(degree); + append_tetra_cell_point( + ref_points, + std::span(x0, static_cast(tdim)), + std::span(x1, static_cast(tdim)), + std::span(x2, static_cast(tdim)), + std::span(x3, static_cast(tdim)), + u, v, w); + } + } + } + } + break; + case cell::type::hexahedron: + for (int k = 1; k < degree; ++k) + { + for (int j = 1; j < degree; ++j) + { + for (int i = 1; i < degree; ++i) + { + append_hex_cell_point( + ref_points, vertices, tdim, + static_cast(i) / static_cast(degree), + static_cast(j) / static_cast(degree), + static_cast(k) / static_cast(degree)); + } + } + } + break; + default: + break; + } + + return ref_points; +} + +template +const std::vector& cached_reference_lagrange_points(cell::type ctype, int degree) +{ + thread_local std::unordered_map, std::vector, RefPointKeyHash> cache; + + RefPointKey key; + key.ctype = ctype; + key.degree = degree; + + auto it = cache.find(key); + if (it != cache.end()) + return it->second; + + auto points = build_reference_lagrange_points(ctype, degree); + auto [inserted_it, inserted] = cache.emplace(key, std::move(points)); + (void)inserted; + return inserted_it->second; +} + +/// Determine the cell::type for a cell in the level-set mesh data. +/// Uses cell_types array if available, otherwise infers from tdim and +/// number of DOFs per cell (assuming equispaced Lagrange). +template +cell::type infer_ls_cell_type(const LevelSetMeshData& md, I cell_id) +{ + if (!md.cell_types.empty()) + { + return md.cell_types[static_cast(cell_id)]; + } + + // Infer from tdim and number of DOFs per cell + const I ndofs = md.cell_num_dofs(cell_id); + const int n = md.degree; + + switch (md.tdim) + { + case 1: + return cell::type::interval; + case 2: + { + // triangle: C(n+2,2) = (n+1)(n+2)/2 + // quad: (n+1)^2 + int tri_dofs = (n + 1) * (n + 2) / 2; + if (ndofs == static_cast(tri_dofs)) + return cell::type::triangle; + else + return cell::type::quadrilateral; + } + case 3: + { + // tet: C(n+3,3) = (n+1)(n+2)(n+3)/6 + // hex: (n+1)^3 + int tet_dofs = (n + 1) * (n + 2) * (n + 3) / 6; + int hex_dofs = (n + 1) * (n + 1) * (n + 1); + if (ndofs == static_cast(tet_dofs)) + return cell::type::tetrahedron; + else if (ndofs == static_cast(hex_dofs)) + return cell::type::hexahedron; + else + { + throw std::runtime_error( + "make_cell_level_set: cannot infer cell type from " + + std::to_string(ndofs) + " DOFs in 3D"); + } + } + default: + throw std::runtime_error( + "make_cell_level_set: unsupported tdim = " + + std::to_string(md.tdim)); + } +} + +} // anonymous namespace + +// --------------------------------------------------------------------------- +// make_cell_level_set +// --------------------------------------------------------------------------- + +template +LevelSetCell +make_cell_level_set(const LevelSetFunction& global_ls, + I cell_id) +{ + LevelSetCell cell_ls; + cell_ls.global_level_set = &global_ls; + cell_ls.cell_id = cell_id; + + // --- Polynomial path: extract nodal values, convert to Bernstein --- + if (global_ls.type == LevelSetType::Polynomial + && global_ls.has_mesh_data() + && global_ls.has_dof_values()) + { + const auto& md = *global_ls.mesh_data; + const int degree = md.degree; + const int gdim = md.gdim; + + // Determine cell type + cell::type ctype = infer_ls_cell_type(md, cell_id); + cell_ls.cell_type = ctype; + cell_ls.gdim = gdim; + cell_ls.tdim = cell::get_tdim(ctype); + + // Get the cell's DOF indices + auto cell_dofs = md.cell_dofs_span(cell_id); + const int ndofs = static_cast(cell_dofs.size()); + const int nv = cell::get_num_vertices(ctype); + if (ndofs < nv) + { + throw std::runtime_error( + "make_cell_level_set: cell dof map is missing vertex dofs"); + } + cell_ls.parent_vertex_coords.resize( + static_cast(nv * gdim), T(0)); + for (int i = 0; i < nv; ++i) + { + const I dof = cell_dofs[static_cast(i)]; + const T* x = md.dof_coordinate(dof); + for (int d = 0; d < gdim; ++d) + { + cell_ls.parent_vertex_coords[ + static_cast(i * gdim + d)] = x[d]; + } + } + + // Extract nodal values from the global DOF array + cell_ls.nodal_values.resize(static_cast(ndofs)); + for (int i = 0; i < ndofs; ++i) + cell_ls.nodal_values[static_cast(i)] + = global_ls.dof_values[static_cast(cell_dofs[static_cast(i)])]; + cell_ls.nodal_order = degree; + const auto& dof_ref = cached_reference_lagrange_points(ctype, degree); + if (static_cast(dof_ref.size()) != ndofs * cell_ls.tdim) + { + throw std::runtime_error( + "make_cell_level_set: cached reference lattice size mismatch"); + } + + // Convert Lagrange nodal values to Bernstein coefficients + cell_ls.bernstein_order = degree; + bernstein::lagrange_to_bernstein( + ctype, degree, + std::span(dof_ref.data(), dof_ref.size()), + std::span(cell_ls.nodal_values), + cell_ls.bernstein_coeffs); + } + + return cell_ls; +} + +// --------------------------------------------------------------------------- +// LevelSetCell::value +// --------------------------------------------------------------------------- + +template +T LevelSetCell::value(std::span xi) const +{ + // Polynomial backend: evaluate the Bernstein expansion + if (!bernstein_coeffs.empty()) + { + return bernstein::evaluate( + cell_type, bernstein_order, + std::span(bernstein_coeffs), xi); + } + + // Analytical fallback: delegate to the global level-set value in physical + // coordinates after mapping xi to x. + if (global_level_set != nullptr + && global_level_set->has_value() + && !parent_vertex_coords.empty()) + { + const auto x = cell::push_forward_affine_map( + cell_type, parent_vertex_coords, gdim, xi); + return global_level_set->value(x.data(), cell_id); + } + + throw std::runtime_error( + "LevelSetCell::value: no Bernstein coefficients available " + "and no analytical fallback available"); +} + +// --------------------------------------------------------------------------- +// LevelSetCell::grad +// --------------------------------------------------------------------------- + +template +void LevelSetCell::grad(std::span xi, std::span g) const +{ + if (static_cast(g.size()) != tdim) + { + throw std::runtime_error( + "LevelSetCell::grad: output gradient has wrong dimension"); + } + + // Polynomial backend: gradient of the Bernstein expansion in reference + // coordinates. This is the gradient of the local approximation phi_h. + if (!bernstein_coeffs.empty()) + { + bernstein::gradient( + cell_type, bernstein_order, + std::span(bernstein_coeffs), xi, g); + return; + } + + // Analytical fallback: LevelSetFunction::grad is physical dphi/dx. + // Pull it back to the parent reference element as dphi/dxi = J^T dphi/dx. + if (global_level_set != nullptr + && global_level_set->has_gradient() + && !parent_vertex_coords.empty()) + { + const auto x = cell::push_forward_affine_map( + cell_type, parent_vertex_coords, gdim, xi); + std::vector grad_phys(static_cast(gdim), T(0)); + global_level_set->grad(x.data(), cell_id, grad_phys.data()); + + const auto cols = cell::jacobian_col_indices(cell_type); + for (int a = 0; a < tdim; ++a) + { + const int va = cols[static_cast(a)]; + if (va < 0) + { + throw std::runtime_error( + "LevelSetCell::grad: invalid affine Jacobian column"); + } + T value = T(0); + for (int r = 0; r < gdim; ++r) + { + const T j_ra = + parent_vertex_coords[ + static_cast(va * gdim + r)] + - parent_vertex_coords[static_cast(r)]; + value += j_ra * grad_phys[static_cast(r)]; + } + g[static_cast(a)] = value; + } + return; + } + + throw std::runtime_error( + "LevelSetCell::grad: no Bernstein coefficients available " + "and no analytical fallback available"); +} + +// --------------------------------------------------------------------------- +// Explicit template instantiations +// --------------------------------------------------------------------------- + +template LevelSetCell +make_cell_level_set(const LevelSetFunction&, int); + +template LevelSetCell +make_cell_level_set(const LevelSetFunction&, int); + +template LevelSetCell +make_cell_level_set(const LevelSetFunction&, long); + +template LevelSetCell +make_cell_level_set(const LevelSetFunction&, long); + +template double LevelSetCell::value(std::span) const; +template float LevelSetCell::value(std::span) const; +template double LevelSetCell::value(std::span) const; +template float LevelSetCell::value(std::span) const; + +template void LevelSetCell::grad(std::span, std::span) const; +template void LevelSetCell::grad(std::span, std::span) const; +template void LevelSetCell::grad(std::span, std::span) const; +template void LevelSetCell::grad(std::span, std::span) const; + +} // namespace cutcells diff --git a/cpp/src/level_set_cell.h b/cpp/src/level_set_cell.h new file mode 100644 index 0000000..dbc34b0 --- /dev/null +++ b/cpp/src/level_set_cell.h @@ -0,0 +1,67 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +#include "adapt_cell.h" +#include "cell_types.h" +#include "level_set.h" +#include "mesh_view.h" + +namespace cutcells +{ + +template +struct LevelSetCell +{ + const LevelSetFunction* global_level_set = nullptr; ///< non-owning view of the global level set function + + int level_set_id = 0 ; ///< identifier for the level set function on this cell (e.g., for multi-level-set scenarios) + + // --- Cell geometry --- + cell::type cell_type = cell::type::point; ///< cell type of the background cell + int gdim = 0; ///< geometric dimension + int tdim = 0; ///< topological dimension + I cell_id = static_cast(-1); ///< background cell index + std::vector parent_vertex_coords; ///< physical parent-cell vertices + + // --- Evaluation interface (uniform for both backends) --- + // Evaluate the level set at a point xi in reference coordinates. + T value(std::span xi) const; + + // Evaluate the gradient at xi in reference coordinates. + void grad(std::span xi, std::span g) const; + + // --- Polynomial backend storage --- + // Bernstein coefficients on this cell. + // For a polynomial level set, the global nodal values are extracted for + // this cell, then converted from the nodal (Lagrange) basis to the + // Bernstein basis on the reference cell. + std::vector bernstein_coeffs; + int bernstein_order = 0; + + // The original nodal values on this cell (before conversion), kept for + // reference and debugging. + std::vector nodal_values; + int nodal_order = 0; +}; + +/// Create a LevelSetCell and AdaptCell for a single background cell. +/// +/// @param global_ls The global level set function. +/// @param mesh The background mesh. +/// @param cell_id Index of the background cell to process. +/// +/// @return A pair of (LevelSetCell, AdaptCell) initialized for the given cell. +template +LevelSetCell +make_cell_level_set(const LevelSetFunction& global_ls, + I cell_id); + +} // namespace cutcells diff --git a/cpp/src/mapping.cpp b/cpp/src/mapping.cpp new file mode 100644 index 0000000..80d6fe8 --- /dev/null +++ b/cpp/src/mapping.cpp @@ -0,0 +1,410 @@ +// Copyright (c) 2024 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT + +#include "mapping.h" + +#include +#include +#include +#include + +namespace cutcells::cell +{ + +namespace +{ + // ------------------------------------------------------------------------- + // Per-cell-type helpers + // ------------------------------------------------------------------------- + + /// Number of first-order vertices per parent cell type (VTK convention). + inline int num_parent_vertices(type cell_type) + { + switch (cell_type) + { + case type::interval: return 2; + case type::triangle: return 3; + case type::tetrahedron: return 4; + case type::quadrilateral: return 4; + case type::hexahedron: return 8; + case type::prism: return 6; + case type::pyramid: return 5; + default: + throw std::invalid_argument("mapping: unsupported parent cell type"); + } + } + + // jacobian_col_indices is now a public inline in mapping.h + + // ------------------------------------------------------------------------- + // Small dense matrix-vector multiply y = J * x + // J is stored column-major: col k occupies J[k*gdim .. (k+1)*gdim - 1] + // ------------------------------------------------------------------------- + + template + inline void mat_vec_1x1(const T* J, const T* x, T* y) + { + y[0] = J[0] * x[0]; + } + + template + inline void mat_vec_2x2(const T* J, const T* x, T* y) + { + // J = | J[0] J[2] | + // | J[1] J[3] | + y[0] = J[0]*x[0] + J[2]*x[1]; + y[1] = J[1]*x[0] + J[3]*x[1]; + } + + template + inline void mat_vec_3x3(const T* J, const T* x, T* y) + { + // J = | J[0] J[3] J[6] | + // | J[1] J[4] J[7] | + // | J[2] J[5] J[8] | + y[0] = J[0]*x[0] + J[3]*x[1] + J[6]*x[2]; + y[1] = J[1]*x[0] + J[4]*x[1] + J[7]*x[2]; + y[2] = J[2]*x[0] + J[5]*x[1] + J[8]*x[2]; + } + + // ------------------------------------------------------------------------- + // Solve J * y = x for y (J column-major, same layout as above) + // ------------------------------------------------------------------------- + + template + inline void solve_1x1(const T* J, const T* x, T* y) + { + y[0] = x[0] / J[0]; + } + + template + inline void solve_2x2(const T* J, const T* x, T* y) + { + // J = | a b | det = a*d - b*c + // | c d | + // column-major: a=J[0] c=J[1] b=J[2] d=J[3] + const T a = J[0], c = J[1], b = J[2], d = J[3]; + const T inv_det = T(1) / (a*d - b*c); + y[0] = inv_det * ( d*x[0] - b*x[1]); + y[1] = inv_det * (-c*x[0] + a*x[1]); + } + + template + inline void solve_3x3(const T* J, const T* x, T* y) + { + // Column-major layout: + // J = | J[0] J[3] J[6] | + // | J[1] J[4] J[7] | + // | J[2] J[5] J[8] | + // + // Cofactors of column 0 (used for det and first row of J^{-1}): + const T C00 = J[4]*J[8] - J[7]*J[5]; // cof(0,0) + const T C10 = -(J[3]*J[8] - J[6]*J[5]); // cof(1,0) + const T C20 = J[3]*J[7] - J[6]*J[4]; // cof(2,0) + + const T det = J[0]*C00 + J[1]*C10 + J[2]*C20; + const T inv_det = T(1) / det; + + // Remaining cofactors: + const T C01 = -(J[1]*J[8] - J[7]*J[2]); // cof(0,1) + const T C11 = J[0]*J[8] - J[6]*J[2]; // cof(1,1) + const T C21 = -(J[0]*J[7] - J[6]*J[1]); // cof(2,1) + const T C02 = J[1]*J[5] - J[4]*J[2]; // cof(0,2) + const T C12 = -(J[0]*J[5] - J[3]*J[2]); // cof(1,2) + const T C22 = J[0]*J[4] - J[3]*J[1]; // cof(2,2) + + // J^{-1}[row i, col j] = cof(j, i) / det + // y = J^{-1} * x + y[0] = inv_det * (C00*x[0] + C10*x[1] + C20*x[2]); + y[1] = inv_det * (C01*x[0] + C11*x[1] + C21*x[2]); + y[2] = inv_det * (C02*x[0] + C12*x[1] + C22*x[2]); + } + + // ------------------------------------------------------------------------- + // Build the column-major Jacobian matrix (gdim x gdim) for the affine map. + // J_out must point to storage for at least gdim*gdim values. + // ------------------------------------------------------------------------- + template + void build_jacobian(type parent_type, + const std::vector& pv, + int gdim, + T* J_out) + { + const auto cols = jacobian_col_indices(parent_type); + const T* v0 = pv.data(); + for (int col = 0; col < gdim; ++col) + { + const T* vi = pv.data() + cols[col] * gdim; + for (int row = 0; row < gdim; ++row) + J_out[col * gdim + row] = vi[row] - v0[row]; + } + } + +} // anonymous namespace + +// ============================================================================= +// Public API implementations +// ============================================================================= + +template +void push_forward_affine(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span X_ref, + std::span x_phys) +{ + assert(X_ref.size() == x_phys.size()); + const int n = static_cast(X_ref.size()) / gdim; + const T* x0 = parent_vertex_coords.data(); + + T J[9] = {}; + build_jacobian(parent_type, parent_vertex_coords, gdim, J); + + if (gdim == 1) + { + for (int i = 0; i < n; ++i) + { + T y[1]; + mat_vec_1x1(J, X_ref.data() + i, y); + x_phys[i] = x0[0] + y[0]; + } + } + else if (gdim == 2) + { + for (int i = 0; i < n; ++i) + { + T y[2]; + mat_vec_2x2(J, X_ref.data() + 2*i, y); + x_phys[2*i ] = x0[0] + y[0]; + x_phys[2*i + 1] = x0[1] + y[1]; + } + } + else // gdim == 3 + { + for (int i = 0; i < n; ++i) + { + T y[3]; + mat_vec_3x3(J, X_ref.data() + 3*i, y); + x_phys[3*i ] = x0[0] + y[0]; + x_phys[3*i + 1] = x0[1] + y[1]; + x_phys[3*i + 2] = x0[2] + y[2]; + } + } +} + +template +std::vector push_forward_affine_map(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span X_ref) +{ + const int tdim = get_tdim(parent_type); + if (tdim <= 0 || tdim > gdim) + throw std::invalid_argument("push_forward_affine_map: invalid tdim/gdim combination"); + if (X_ref.size() % static_cast(tdim) != 0) + { + throw std::invalid_argument( + "push_forward_affine_map: X_ref size must be a multiple of the parent tdim"); + } + + const int n = static_cast(X_ref.size()) / tdim; + std::vector x_phys(static_cast(n * gdim), T(0)); + const T* x0 = parent_vertex_coords.data(); + const auto cols = jacobian_col_indices(parent_type); + + for (int i = 0; i < n; ++i) + { + const T* X = X_ref.data() + i * tdim; + T* x = x_phys.data() + i * gdim; + + for (int d = 0; d < gdim; ++d) + x[d] = x0[d]; + + for (int col = 0; col < tdim; ++col) + { + const T* vi = parent_vertex_coords.data() + cols[col] * gdim; + for (int d = 0; d < gdim; ++d) + x[d] += X[col] * (vi[d] - x0[d]); + } + } + + return x_phys; +} + +template +void pull_back_affine(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span x_phys, + std::span X_ref) +{ + assert(x_phys.size() == X_ref.size()); + const int n = static_cast(x_phys.size()) / gdim; + const T* x0 = parent_vertex_coords.data(); + + T J[9] = {}; + build_jacobian(parent_type, parent_vertex_coords, gdim, J); + + if (gdim == 1) + { + for (int i = 0; i < n; ++i) + { + T rhs = x_phys[i] - x0[0]; + solve_1x1(J, &rhs, X_ref.data() + i); + } + } + else if (gdim == 2) + { + T rhs[2]; + for (int i = 0; i < n; ++i) + { + rhs[0] = x_phys[2*i ] - x0[0]; + rhs[1] = x_phys[2*i + 1] - x0[1]; + solve_2x2(J, rhs, X_ref.data() + 2*i); + } + } + else // gdim == 3 + { + T rhs[3]; + for (int i = 0; i < n; ++i) + { + rhs[0] = x_phys[3*i ] - x0[0]; + rhs[1] = x_phys[3*i + 1] - x0[1]; + rhs[2] = x_phys[3*i + 2] - x0[2]; + solve_3x3(J, rhs, X_ref.data() + 3*i); + } + } +} + +template +void compute_physical_cut_vertices(CutCell& cut_cell) +{ + const int gdim = cut_cell._gdim; + const int n_verts = static_cast(cut_cell._vertex_coords.size()) / gdim; + cut_cell._vertex_coords_phys.resize(n_verts * gdim); + + push_forward_affine(cut_cell._parent_cell_type, + cut_cell._parent_vertex_coords, + gdim, + std::span(cut_cell._vertex_coords), + std::span(cut_cell._vertex_coords_phys)); +} + +template +void complete_from_physical(CutCell& cut_cell) +{ + const int gdim = cut_cell._gdim; + const int n_verts = static_cast(cut_cell._vertex_coords.size()) / gdim; + + // Step 1: move the raw physical output from the cutter into _vertex_coords_phys. + cut_cell._vertex_coords_phys = std::move(cut_cell._vertex_coords); + + // Step 2: pull back the physical coords to reference space and store in _vertex_coords. + cut_cell._vertex_coords.resize(n_verts * gdim); + pull_back_affine(cut_cell._parent_cell_type, + cut_cell._parent_vertex_coords, + gdim, + std::span(cut_cell._vertex_coords_phys), + std::span(cut_cell._vertex_coords)); +} + +// ============================================================================= +// affine_volume_factor +// ============================================================================= + +template +T affine_volume_factor(type cell_type, const T* phys_verts, int gdim) +{ + const int tdim = get_tdim(cell_type); + const auto cols = jacobian_col_indices(cell_type); + const T* v0 = phys_verts; + + if (tdim == gdim) + { + // Square Jacobian — build column-major gdim×gdim matrix and take |det|. + T J[9] = {}; + for (int c = 0; c < tdim; ++c) + { + const T* vi = phys_verts + cols[c] * gdim; + for (int r = 0; r < gdim; ++r) + J[c * gdim + r] = vi[r] - v0[r]; + } + if (gdim == 1) + { + return std::abs(J[0]); + } + else if (gdim == 2) + { + // det of 2×2 column-major J: col0=(J[0],J[1]), col1=(J[2],J[3]) + const T det = J[0]*J[3] - J[2]*J[1]; + return std::abs(det); + } + else // gdim == 3 + { + // det of 3×3 column-major J by cofactor expansion along first row + const T det = J[0]*(J[4]*J[8] - J[7]*J[5]) + - J[1]*(J[3]*J[8] - J[6]*J[5]) + + J[2]*(J[3]*J[7] - J[6]*J[4]); + return std::abs(det); + } + } + else if (tdim == 1) + { + // Embedded edge: J is a single column vector of length gdim. + const T* vi = phys_verts + cols[0] * gdim; + T norm2 = T(0); + for (int r = 0; r < gdim; ++r) + { + const T d = vi[r] - v0[r]; + norm2 += d * d; + } + return std::sqrt(norm2); + } + else if (tdim == 2 && gdim == 3) + { + // Embedded surface: J has two columns (3-vectors). Volume factor = |j0 × j1|. + const T* vi0 = phys_verts + cols[0] * gdim; + const T* vi1 = phys_verts + cols[1] * gdim; + const T j0[3] = {vi0[0]-v0[0], vi0[1]-v0[1], vi0[2]-v0[2]}; + const T j1[3] = {vi1[0]-v0[0], vi1[1]-v0[1], vi1[2]-v0[2]}; + const T cx = j0[1]*j1[2] - j0[2]*j1[1]; + const T cy = j0[2]*j1[0] - j0[0]*j1[2]; + const T cz = j0[0]*j1[1] - j0[1]*j1[0]; + return std::sqrt(cx*cx + cy*cy + cz*cz); + } + else + { + throw std::invalid_argument("affine_volume_factor: unsupported tdim/gdim combination"); + } +} + +// ============================================================================= +// Explicit instantiations for double and float +// ============================================================================= +template void push_forward_affine(type, const std::vector&, int, + std::span, std::span); +template void push_forward_affine(type, const std::vector&, int, + std::span, std::span); +template std::vector push_forward_affine_map( + type, const std::vector&, int, std::span); +template std::vector push_forward_affine_map( + type, const std::vector&, int, std::span); + +template void pull_back_affine(type, const std::vector&, int, + std::span, std::span); +template void pull_back_affine(type, const std::vector&, int, + std::span, std::span); + +template void compute_physical_cut_vertices(CutCell&); +template void compute_physical_cut_vertices(CutCell&); + +template void complete_from_physical(CutCell&); +template void complete_from_physical(CutCell&); + +template double affine_volume_factor(type, const double*, int); +template float affine_volume_factor(type, const float*, int); + +} // namespace cutcells::cell diff --git a/cpp/src/mapping.h b/cpp/src/mapping.h new file mode 100644 index 0000000..d5f2d34 --- /dev/null +++ b/cpp/src/mapping.h @@ -0,0 +1,157 @@ +// Copyright (c) 2024 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "cut_cell.h" +#include "cell_types.h" + +#include +#include +#include +#include +#include + +namespace cutcells::cell +{ + +/// @brief Indices into parent_vertex_coords for the Jacobian columns. +/// +/// Column k of J = parent_vertex[ col[k] ] - parent_vertex[0]. +/// Up to tdim columns; unused entries are -1. +/// +/// Choices follow VTK first-order vertex ordering so that J is diagonal +/// (and thus the identity) for the unit reference cell. +/// +/// interval: v0=0 v1=1 → col[0]=1 +/// triangle: v0=(0,0) v1=(1,0) v2=(0,1) → col={1,2} +/// tetrahedron: v0=(0,0,0) v1=(1,0,0) v2=(0,1,0) v3=(0,0,1) → col={1,2,3} +/// quadrilateral: v0=(0,0) v1=(1,0) v2=(1,1) v3=(0,1) → col={1,3} +/// hexahedron: v0=(0,0,0) v1=(1,0,0) v3=(0,1,0) v4=(0,0,1) → col={1,3,4} +/// prism: v0=(0,0,0) v1=(1,0,0) v2=(0,1,0) v3=(0,0,1) → col={1,2,3} +/// pyramid: v0=(0,0,0) v1=(1,0,0) v3=(0,1,0) v4=apex → col={1,3,4} +inline std::array jacobian_col_indices(type cell_type) +{ + switch (cell_type) + { + case type::interval: return {1, -1, -1}; + case type::triangle: return {1, 2, -1}; + case type::tetrahedron: return {1, 2, 3}; + case type::quadrilateral: return {1, 3, -1}; + case type::hexahedron: return {1, 3, 4}; + case type::prism: return {1, 2, 3}; + case type::pyramid: return {1, 3, 4}; + default: + throw std::invalid_argument("mapping: unsupported parent cell type"); + } +} + +/// @brief Compute the affine volume scaling factor |det J| (or √det(JᵀJ) for +/// embedded cells) for a single cell. +/// +/// For volume cells (tdim == gdim) returns |det J|, the absolute value of +/// the Jacobian determinant of the affine map from the reference element to +/// physical space. +/// +/// For surface/embedded cells (tdim < gdim) returns the Gramian root +/// √(det(JᵀJ)) which gives the correct area / length scaling factor. +/// +/// @param cell_type VTK cell type +/// @param phys_verts flat physical vertex coordinates in VTK ordering +/// (nv * gdim values) +/// @param gdim geometric dimension of the embedding space (1, 2, or 3) +/// @returns volume scaling factor ≥ 0 +template +T affine_volume_factor(type cell_type, const T* phys_verts, int gdim); + +/// @brief Push all cut vertices from parent reference space into physical space. +/// +/// Uses the affine map x_phys = x0 + J * X_ref where J is built from the +/// first-order parent cell vertex differences (VTK ordering) and x0 is the first +/// parent physical vertex. +/// +/// Precondition : _vertex_coords holds reference coordinates in the parent +/// reference cell; _parent_vertex_coords and _parent_cell_type +/// are set. +/// Postcondition : _vertex_coords_phys is allocated and filled. +/// +/// Note: supported for gdim == topological dimension of parent (volume cells). +template +void compute_physical_cut_vertices(CutCell& cut_cell); + +/// @brief Complete a cut cell that was computed in physical space. +/// +/// The cutting routine wrote physical coordinates into _vertex_coords. +/// This function: +/// 1. moves _vertex_coords → _vertex_coords_phys +/// 2. pulls _vertex_coords_phys back through J * X = x - x0 to overwrite +/// _vertex_coords with the reference coordinates. +/// +/// After this call _vertex_coords is always in reference space and +/// _vertex_coords_phys is always in physical space. +/// +/// Precondition : _vertex_coords holds raw physical coordinates from the cutter; +/// _parent_vertex_coords and _parent_cell_type are set. +/// Postcondition : _vertex_coords = reference coordinates (parent ref. space) +/// _vertex_coords_phys = physical coordinates +/// +/// Note: supported for gdim == topological dimension of parent (volume cells). +template +void complete_from_physical(CutCell& cut_cell); + +/// @brief Affine push-forward for a batch of n reference points. +/// +/// Maps X_ref (flat, n*gdim) from parent reference space to physical space +/// x_phys (flat, n*gdim) via x = x0 + J * X where J is derived from +/// parent_vertex_coords and parent_type in VTK vertex ordering. +/// +/// @param parent_type cell type of the parent element +/// @param parent_vertex_coords flat physical vertex coords in VTK ordering +/// @param gdim geometric / topological dimension (must equal +/// the topological dimension of parent_type) +/// @param X_ref flat input: n * gdim reference coordinates +/// @param x_phys flat output: n * gdim physical coordinates +template +void push_forward_affine(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span X_ref, + std::span x_phys); + +/// @brief Affine push-forward for parent-reference points when tdim <= gdim. +/// +/// Maps points in the parent reference space (dimension = get_tdim(parent_type)) +/// to physical space (dimension = gdim). This supports embedded cells, e.g. +/// triangles in 3D or intervals in 2D/3D. +/// +/// @param parent_type cell type of the parent element +/// @param parent_vertex_coords flat physical vertex coords in VTK ordering +/// @param gdim physical embedding dimension +/// @param X_ref flat input: n * tdim reference coordinates +/// @returns flat output: n * gdim physical coordinates +template +std::vector push_forward_affine_map(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span X_ref); + +/// @brief Affine pullback for a batch of n physical points. +/// +/// Maps x_phys (flat, n*gdim) from physical space to parent reference space +/// X_ref (flat, n*gdim) by solving J * X = x - x0. +/// +/// @param parent_type cell type of the parent element +/// @param parent_vertex_coords flat physical vertex coords in VTK ordering +/// @param gdim geometric / topological dimension +/// @param x_phys flat input: n * gdim physical coordinates +/// @param X_ref flat output: n * gdim reference coordinates +template +void pull_back_affine(type parent_type, + const std::vector& parent_vertex_coords, + int gdim, + std::span x_phys, + std::span X_ref); + +} // namespace cutcells::cell diff --git a/cpp/src/mesh_view.h b/cpp/src/mesh_view.h new file mode 100644 index 0000000..898f14c --- /dev/null +++ b/cpp/src/mesh_view.h @@ -0,0 +1,95 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include + +#include "cell_types.h" + +namespace cutcells +{ + +template +struct MeshView +{ + int gdim = 0; + int tdim = 0; + + // Flattened coordinates: [x0_0, x0_1, ..., x1_0, x1_1, ...] + std::span coordinates; + + // CSR-like cell storage + std::span connectivity; + std::span offsets; + + // Cell types, one per cell in cutcells cell::type enum numbering (not VTK codes). + std::span cell_types; + + // True when the vertex connectivity of this mesh uses VTK vertex ordering. + // Set by the Python boundary when constructing from VTK data. + // Used internally to apply the VTK→Basix vertex permutation. + bool vtk_vertex_order = false; + + // Optional keep-alive anchor for Python / external memory + std::shared_ptr owner; + + I num_nodes() const + { + if (gdim <= 0) + return 0; + return static_cast(coordinates.size() / static_cast(gdim)); + } + + I num_cells() const + { + if (offsets.empty()) + return 0; + return static_cast(offsets.size() - 1); + } + + bool has_cell_types() const + { + return !cell_types.empty(); + } + + const T* node(I node_id) const + { + return coordinates.data() + static_cast(node_id) * static_cast(gdim); + } + + I cell_num_nodes(I cell_id) const + { + return offsets[static_cast(cell_id) + 1] - offsets[static_cast(cell_id)]; + } + + std::span cell_nodes(I cell_id) const + { + const std::size_t begin = static_cast(offsets[static_cast(cell_id)]); + const std::size_t end = static_cast(offsets[static_cast(cell_id) + 1]); + return connectivity.subspan(begin, end - begin); + } + + I cell_node(I cell_id, I local_id) const + { + const std::size_t pos = static_cast(offsets[static_cast(cell_id)]) + + static_cast(local_id); + return connectivity[pos]; + } + + cell::type cell_type(I cell_id) const + { + if (cell_types.empty()) + throw std::runtime_error("MeshView::cell_type not available"); + return cell_types[static_cast(cell_id)]; + } +}; + +} // namespace cutcells \ No newline at end of file diff --git a/cpp/src/prism_midpoint_split.cpp b/cpp/src/prism_midpoint_split.cpp new file mode 100644 index 0000000..f6a10d5 --- /dev/null +++ b/cpp/src/prism_midpoint_split.cpp @@ -0,0 +1,115 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT + +#include "prism_midpoint_split.h" + +#include +#include + +namespace cutcells::cell::prism_midpoint +{ + +namespace +{ + +constexpr std::array prism_edge_list = {{ + {0, 1}, {1, 2}, {2, 0}, + {3, 4}, {4, 5}, {5, 3}, + {0, 3}, {1, 4}, {2, 5}, +}}; + +[[noreturn]] void throw_invalid_prism_tokens(std::span prism_tokens, + const std::string& message) +{ + std::string full = "prism_midpoint_split: " + message + " tokens=["; + for (std::size_t i = 0; i < prism_tokens.size(); ++i) + { + if (i > 0) + full += ","; + full += std::to_string(prism_tokens[i]); + } + full += "]"; + throw std::runtime_error(full); +} + +} // namespace + +bool is_root_token(int token) +{ + return token >= 0 && token < 100; +} + +std::span prism_edges() +{ + return std::span(prism_edge_list.data(), prism_edge_list.size()); +} + +family classify_family(std::span prism_tokens) +{ + if (prism_tokens.size() != 6) + throw std::invalid_argument("classify_family: expected 6 prism tokens"); + + int num_roots = 0; + for (const int token : prism_tokens) + num_roots += is_root_token(token) ? 1 : 0; + + if (num_roots == 3) + return family::roots3; + if (num_roots == 4) + return family::roots4; + + throw_invalid_prism_tokens( + prism_tokens, + "tetra-derived prism must contain exactly 3 or 4 root vertices"); +} + +PrismRoleAnalysis analyze_tetra_derived_prism(std::span prism_tokens) +{ + if (prism_tokens.size() != 6) + throw std::invalid_argument("analyze_tetra_derived_prism: expected 6 prism tokens"); + + PrismRoleAnalysis out; + out.split_family = classify_family(prism_tokens); + + for (int local_id = 0; local_id < 6; ++local_id) + { + const int token = prism_tokens[static_cast(local_id)]; + if (is_root_token(token)) + { + const std::size_t idx = static_cast(out.num_roots); + out.root_local_ids[idx] = local_id; + ++out.num_roots; + } + else + { + const std::size_t idx = static_cast(out.num_non_roots); + out.non_root_local_ids[idx] = local_id; + ++out.num_non_roots; + } + } + + const auto edges = prism_edges(); + for (const LocalEdge edge : edges) + { + const bool a_root = is_root_token(prism_tokens[static_cast(edge.a)]); + const bool b_root = is_root_token(prism_tokens[static_cast(edge.b)]); + if (!a_root && !b_root) + out.midpoint_edges.push_back(edge); + } + + const std::size_t expected_midpoints + = (out.split_family == family::roots3) ? std::size_t(3) : std::size_t(1); + if (out.midpoint_edges.size() != expected_midpoints) + { + throw_invalid_prism_tokens( + prism_tokens, + "unexpected number of uncut non-root prism edges for midpoint insertion"); + } + + return out; +} + +} // namespace cutcells::cell::prism_midpoint diff --git a/cpp/src/prism_midpoint_split.h b/cpp/src/prism_midpoint_split.h new file mode 100644 index 0000000..7f01da8 --- /dev/null +++ b/cpp/src/prism_midpoint_split.h @@ -0,0 +1,471 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include + +namespace cutcells::cell::prism_midpoint +{ + +/// Prism family induced by cutting a tetrahedron. +/// +/// roots3: +/// The prism carries three root vertices and three original tetra vertices. +/// The quasi-normal midpoint strategy inserts one midpoint on each prism edge +/// connecting two original vertices. +/// +/// roots4: +/// The prism carries four root vertices and two original tetra vertices. +/// The quasi-normal midpoint strategy inserts one midpoint on the single prism +/// edge connecting the two original vertices. +enum class family +{ + roots3, + roots4, +}; + +/// Local prism edge with local vertex ids in the prism ordering used by the +/// tetrahedron LUT subcell definition. +struct LocalEdge +{ + int a = -1; + int b = -1; +}; + +/// Static classification of a tetra-derived prism. +struct PrismRoleAnalysis +{ + family split_family = family::roots3; + std::array root_local_ids = {-1, -1, -1, -1, -1, -1}; + int num_roots = 0; + std::array non_root_local_ids = {-1, -1, -1, -1, -1, -1}; + int num_non_roots = 0; + std::vector midpoint_edges; +}; + +/// True for tetra-cut root tokens. In the current tetra LUT these are the +/// intersection-point edge ids 0..5. Original tetra vertices use 100+vid. +bool is_root_token(int token); + +/// Prism edge list in the local ordering expected by the tetrahedron cutter and +/// by cutcells::cell::triangulation(type::prism, ...). +std::span prism_edges(); + +/// Classify a tetra-derived prism purely from its 6 tokens. +family classify_family(std::span prism_tokens); + +/// Return root/non-root local ids and the default uncut-edge midpoint placement +/// suggested by the tetra-prism analysis. +PrismRoleAnalysis analyze_tetra_derived_prism(std::span prism_tokens); + +inline int parent_tetrahedron_edge_token(int token_a, int token_b) +{ + if (is_root_token(token_a) || is_root_token(token_b)) + { + throw std::runtime_error( + "parent_tetrahedron_edge_token: midpoint endpoints must be original vertices"); + } + + const int a = token_a - 100; + const int b = token_b - 100; + if (a < 0 || a >= 4 || b < 0 || b >= 4) + { + throw std::runtime_error( + "parent_tetrahedron_edge_token: invalid original-vertex token"); + } + + constexpr std::array parent_edges = {{ + {0, 1}, {1, 2}, {2, 0}, {0, 3}, {1, 3}, {2, 3}, + }}; + for (std::size_t e = 0; e < parent_edges.size(); ++e) + { + const LocalEdge edge = parent_edges[e]; + if ((edge.a == a && edge.b == b) || (edge.a == b && edge.b == a)) + return static_cast(e); + } + + throw std::runtime_error("parent_tetrahedron_edge_token: parent edge not found"); +} + +template +struct MidpointInsertionResult +{ + PrismRoleAnalysis analysis; + std::vector added_vertex_coords; // flat coord_dim storage + std::vector added_vertex_tokens; // synthetic tokens chosen by caller + std::vector added_vertex_edges; + std::vector> tets; // token-based tetrahedra +}; + +/// Insert the default midpoint set for a tetra-derived prism. +/// +/// The returned midpoints are not yet wired into any tetra connectivity. This +/// helper exists so the new prism strategy can be implemented in a standalone +/// module first, then integrated at the prism-to-tet call sites. +/// +/// @param prism_vertex_coords flat 6*coord_dim local coordinate storage +/// @param coord_dim prism coordinate dimension, expected 3 for tetra cuts +/// @param prism_tokens 6 tokens from the tetra LUT prism subcell +/// @param next_token_base caller-owned synthetic-token counter; incremented for +/// each created midpoint +template +MidpointInsertionResult create_default_midpoints( + std::span prism_vertex_coords, + int coord_dim, + std::span prism_tokens, + int& next_token_base) +{ + if (coord_dim <= 0) + throw std::invalid_argument("create_default_midpoints: coord_dim must be positive"); + if (prism_vertex_coords.size() != static_cast(6 * coord_dim)) + { + throw std::invalid_argument( + "create_default_midpoints: expected 6 prism vertices in flat storage"); + } + if (prism_tokens.size() != 6) + throw std::invalid_argument("create_default_midpoints: expected 6 prism tokens"); + + MidpointInsertionResult result; + result.analysis = analyze_tetra_derived_prism(prism_tokens); + result.added_vertex_coords.reserve( + static_cast(result.analysis.midpoint_edges.size() * coord_dim)); + result.added_vertex_tokens.reserve(result.analysis.midpoint_edges.size()); + result.added_vertex_edges.reserve(result.analysis.midpoint_edges.size()); + + for (const LocalEdge edge : result.analysis.midpoint_edges) + { + result.added_vertex_tokens.push_back(parent_tetrahedron_edge_token( + prism_tokens[static_cast(edge.a)], + prism_tokens[static_cast(edge.b)])); + result.added_vertex_edges.push_back(edge); + for (int d = 0; d < coord_dim; ++d) + { + const T xa = prism_vertex_coords[static_cast(edge.a * coord_dim + d)]; + const T xb = prism_vertex_coords[static_cast(edge.b * coord_dim + d)]; + result.added_vertex_coords.push_back(T(0.5) * (xa + xb)); + } + } + (void) next_token_base; + + return result; +} + +template +T signed_tet_jacobian(std::span vertex_coords, + int coord_dim, + const std::array& tet_local_ids); + +template +void orient_tet_positive(std::span vertex_coords, + int coord_dim, + std::array& tet_local_ids); + +/// Build the standalone tetrahedral split for a tetra-derived prism using the +/// default midpoint insertion pattern for the detected family. +/// +/// The returned tetrahedra are expressed in tokens: original prism tokens are +/// reused and new midpoint vertices receive synthetic tokens chosen by the +/// caller. This lets the tetrahedron cutter integrate the result by only +/// appending the new coordinates and extending its token->local-index map. +template +MidpointInsertionResult split_tetra_derived_prism( + std::span prism_vertex_coords, + int coord_dim, + std::span prism_tokens, + int& next_token_base) +{ + constexpr std::array, 6> tetra_edges = {{ + {{0, 1}}, + {{1, 2}}, + {{2, 0}}, + {{0, 3}}, + {{1, 3}}, + {{2, 3}}, + }}; + + MidpointInsertionResult result = create_default_midpoints( + prism_vertex_coords, coord_dim, prism_tokens, next_token_base); + + std::array canonical_to_actual = {0, 1, 2, 3, 4, 5}; + + auto root_local_for_original_local = [&](int non_root_local_id) -> int + { + const int original_token = prism_tokens[static_cast(non_root_local_id)]; + if (is_root_token(original_token)) + { + throw std::runtime_error( + "split_tetra_derived_prism: expected original tetra vertex token for non-root prism vertex"); + } + + const int original_vertex = original_token - 100; + for (int i = 0; i < result.analysis.num_roots; ++i) + { + const int root_local_id = result.analysis.root_local_ids[static_cast(i)]; + const int root_token = prism_tokens[static_cast(root_local_id)]; + const auto& edge = tetra_edges[static_cast(root_token)]; + if (edge[0] == original_vertex || edge[1] == original_vertex) + return root_local_id; + } + + throw std::runtime_error( + "split_tetra_derived_prism: failed to match original prism vertex to a root edge"); + }; + + if (result.analysis.split_family == family::roots3) + { + const bool bottom_non_roots + = result.analysis.num_non_roots == 3 + && result.analysis.non_root_local_ids[0] == 0 + && result.analysis.non_root_local_ids[1] == 1 + && result.analysis.non_root_local_ids[2] == 2; + const bool top_non_roots + = result.analysis.num_non_roots == 3 + && result.analysis.non_root_local_ids[0] == 3 + && result.analysis.non_root_local_ids[1] == 4 + && result.analysis.non_root_local_ids[2] == 5; + + if (bottom_non_roots) + { + canonical_to_actual = { + 0, + 1, + 2, + root_local_for_original_local(0), + root_local_for_original_local(1), + root_local_for_original_local(2), + }; + } + else if (top_non_roots) + { + canonical_to_actual = { + 3, + 4, + 5, + root_local_for_original_local(3), + root_local_for_original_local(4), + root_local_for_original_local(5), + }; + } + else + { + throw std::runtime_error( + "split_tetra_derived_prism: roots3 family expected one full prism triangle of non-root vertices"); + } + } + else + { + if (result.analysis.num_non_roots != 2) + { + throw std::runtime_error( + "split_tetra_derived_prism: roots4 family expected exactly two non-root vertices"); + } + + const int a = result.analysis.non_root_local_ids[0]; + const int b = result.analysis.non_root_local_ids[1]; + + int k = -1; + if ((a == 0 && b == 3) || (a == 3 && b == 0)) + k = 0; + else if ((a == 1 && b == 4) || (a == 4 && b == 1)) + k = 1; + else if ((a == 2 && b == 5) || (a == 5 && b == 2)) + k = 2; + + if (k < 0) + { + throw std::runtime_error( + "split_tetra_derived_prism: roots4 family expected non-root vertices on one prism vertical edge"); + } + + canonical_to_actual = { + k, + (k + 1) % 3, + (k + 2) % 3, + k + 3, + ((k + 1) % 3) + 3, + ((k + 2) % 3) + 3, + }; + } + + std::vector all_tokens(6); + for (int i = 0; i < 6; ++i) + all_tokens[static_cast(i)] = prism_tokens[static_cast(i)]; + for (int token : result.added_vertex_tokens) + all_tokens.push_back(token); + + std::vector all_coords(prism_vertex_coords.begin(), prism_vertex_coords.end()); + all_coords.insert( + all_coords.end(), + result.added_vertex_coords.begin(), + result.added_vertex_coords.end()); + + auto midpoint_local_id_for_actual_edge = [&](int actual_a, int actual_b) -> int + { + for (std::size_t i = 0; i < result.added_vertex_edges.size(); ++i) + { + const LocalEdge edge = result.added_vertex_edges[i]; + const bool matches + = (edge.a == actual_a && edge.b == actual_b) + || (edge.a == actual_b && edge.b == actual_a); + if (matches) + return 6 + static_cast(i); + } + + throw std::runtime_error( + "split_tetra_derived_prism: failed to match canonical midpoint edge"); + }; + + auto map_canonical_local_id = [&](int canonical_id) -> int + { + if (canonical_id < 6) + return canonical_to_actual[static_cast(canonical_id)]; + + if (result.analysis.split_family == family::roots3) + { + switch (canonical_id) + { + case 6: + return midpoint_local_id_for_actual_edge( + canonical_to_actual[0], canonical_to_actual[1]); + case 7: + return midpoint_local_id_for_actual_edge( + canonical_to_actual[1], canonical_to_actual[2]); + case 8: + return midpoint_local_id_for_actual_edge( + canonical_to_actual[2], canonical_to_actual[0]); + default: + break; + } + } + else + { + if (canonical_id == 6) + { + return midpoint_local_id_for_actual_edge( + canonical_to_actual[0], canonical_to_actual[3]); + } + } + + throw std::runtime_error("split_tetra_derived_prism: unsupported canonical local id"); + }; + + std::vector> canonical_tets; + if (result.analysis.split_family == family::roots3) + { + // Build the three original-vertex corner tetrahedra first. + // + // The remaining central cell has vertices {3,4,5,6,7,8}: + // - {3,4,5} are the root vertices (one on each tetra edge) + // - {6,7,8} are midpoints on the non-root triangle edges + // + // This remaining polyhedron is a triangular antiprism with boundary + // triangles: + // - top: {3,4,5} + // - bottom: {6,7,8} + // - sides: {3,4,6}, {4,5,7}, {5,3,8}, {3,6,8}, {4,7,6}, {5,8,7} + // + // A 4-tet decomposition (total 7 tets incl. corners) is required here: + // a 3-tet "prism" split does not cover the antiprism volume. + canonical_tets = { + std::array{0, 6, 8, 3}, + std::array{1, 7, 6, 4}, + std::array{2, 8, 7, 5}, + // Central triangular antiprism {3,4,5,6,7,8} split into 4 tets. + // This corresponds to inserting the internal diagonal (3,7). + std::array{3, 4, 6, 7}, + std::array{3, 5, 8, 7}, + std::array{3, 4, 5, 7}, + std::array{3, 6, 8, 7}, + }; + } + else + { + // Delaunay-style split on the canonical prism with one midpoint on the + // non-root edge (0,3). + canonical_tets = { + std::array{6, 2, 1, 0}, + std::array{6, 5, 4, 3}, + std::array{6, 4, 2, 1}, + std::array{6, 5, 4, 2}, + }; + } + + result.tets.clear(); + result.tets.reserve(canonical_tets.size()); + + for (std::array tet : canonical_tets) + { + std::array local_ids = { + map_canonical_local_id(tet[0]), + map_canonical_local_id(tet[1]), + map_canonical_local_id(tet[2]), + map_canonical_local_id(tet[3]), + }; + orient_tet_positive( + std::span(all_coords.data(), all_coords.size()), + coord_dim, + local_ids); + + std::array token_tet = { + all_tokens[static_cast(local_ids[0])], + all_tokens[static_cast(local_ids[1])], + all_tokens[static_cast(local_ids[2])], + all_tokens[static_cast(local_ids[3])], + }; + result.tets.push_back(token_tet); + } + + return result; +} + +/// Signed tetra determinant in local coordinates. Positive means the tet +/// orientation is consistent with the ambient local coordinate frame. +template +T signed_tet_jacobian(std::span vertex_coords, + int coord_dim, + const std::array& tet_local_ids) +{ + if (coord_dim != 3) + throw std::invalid_argument("signed_tet_jacobian: coord_dim must be 3"); + + auto coord = [&](int local_id, int d) -> T + { + return vertex_coords[static_cast(local_id * coord_dim + d)]; + }; + + const T ax = coord(tet_local_ids[1], 0) - coord(tet_local_ids[0], 0); + const T ay = coord(tet_local_ids[1], 1) - coord(tet_local_ids[0], 1); + const T az = coord(tet_local_ids[1], 2) - coord(tet_local_ids[0], 2); + + const T bx = coord(tet_local_ids[2], 0) - coord(tet_local_ids[0], 0); + const T by = coord(tet_local_ids[2], 1) - coord(tet_local_ids[0], 1); + const T bz = coord(tet_local_ids[2], 2) - coord(tet_local_ids[0], 2); + + const T cx = coord(tet_local_ids[3], 0) - coord(tet_local_ids[0], 0); + const T cy = coord(tet_local_ids[3], 1) - coord(tet_local_ids[0], 1); + const T cz = coord(tet_local_ids[3], 2) - coord(tet_local_ids[0], 2); + + return ax * (by * cz - bz * cy) + - ay * (bx * cz - bz * cx) + + az * (bx * cy - by * cx); +} + +/// Flip the last two vertices if needed so the tetra has non-negative +/// orientation in the local coordinate frame. +template +void orient_tet_positive(std::span vertex_coords, + int coord_dim, + std::array& tet_local_ids) +{ + if (signed_tet_jacobian(vertex_coords, coord_dim, tet_local_ids) < T(0)) + std::swap(tet_local_ids[2], tet_local_ids[3]); +} + +} // namespace cutcells::cell::prism_midpoint diff --git a/cpp/src/quad_midpoint_split.cpp b/cpp/src/quad_midpoint_split.cpp new file mode 100644 index 0000000..8241864 --- /dev/null +++ b/cpp/src/quad_midpoint_split.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT + +#include "quad_midpoint_split.h" + +#include + +namespace cutcells::cell::quad_midpoint +{ + +namespace +{ + +[[noreturn]] void throw_invalid_quad_tokens(std::span quad_tokens, + const std::string& message) +{ + std::string full = "quad_midpoint_split: " + message + " tokens=["; + for (std::size_t i = 0; i < quad_tokens.size(); ++i) + { + if (i > 0) + full += ","; + full += std::to_string(quad_tokens[i]); + } + full += "]"; + throw std::runtime_error(full); +} + +} // namespace + +bool is_root_token(int token) +{ + return token >= 0 && token < 100; +} + +QuadRoleAnalysis analyze_triangle_derived_quadrilateral( + std::span quad_tokens) +{ + if (quad_tokens.size() != 4) + { + throw std::invalid_argument( + "analyze_triangle_derived_quadrilateral: expected 4 quadrilateral tokens"); + } + + QuadRoleAnalysis out; + + for (int local_id = 0; local_id < 4; ++local_id) + { + const int token = quad_tokens[static_cast(local_id)]; + if (is_root_token(token)) + { + out.root_local_ids[static_cast(out.num_roots)] = local_id; + ++out.num_roots; + } + else + { + out.non_root_local_ids[static_cast(out.num_non_roots)] = local_id; + ++out.num_non_roots; + } + } + + if (out.num_roots != 2 || out.num_non_roots != 2) + { + throw_invalid_quad_tokens( + quad_tokens, + "triangle-derived quadrilateral must contain exactly 2 root and 2 non-root vertices"); + } + + bool found_canonical_order = false; + for (int start = 0; start < 4; ++start) + { + const int v0 = start; + const int v1 = (start + 1) % 4; + const int v2 = (start + 2) % 4; + const int v3 = (start + 3) % 4; + if (is_root_token(quad_tokens[static_cast(v0)]) + && is_root_token(quad_tokens[static_cast(v1)]) + && !is_root_token(quad_tokens[static_cast(v2)]) + && !is_root_token(quad_tokens[static_cast(v3)])) + { + out.canonical_to_actual = {v0, v1, v2, v3}; + out.midpoint_edge = {v2, v3}; + found_canonical_order = true; + break; + } + } + + if (!found_canonical_order) + { + throw_invalid_quad_tokens( + quad_tokens, + "expected cyclic ordering with two consecutive root vertices and two consecutive non-root vertices"); + } + + return out; +} + +} // namespace cutcells::cell::quad_midpoint diff --git a/cpp/src/quad_midpoint_split.h b/cpp/src/quad_midpoint_split.h new file mode 100644 index 0000000..b0f4d8e --- /dev/null +++ b/cpp/src/quad_midpoint_split.h @@ -0,0 +1,161 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include + +namespace cutcells::cell::quad_midpoint +{ + +struct LocalEdge +{ + int a = -1; + int b = -1; +}; + +struct QuadRoleAnalysis +{ + std::array root_local_ids = {-1, -1, -1, -1}; + int num_roots = 0; + std::array non_root_local_ids = {-1, -1, -1, -1}; + int num_non_roots = 0; + std::array canonical_to_actual = {0, 1, 2, 3}; + LocalEdge midpoint_edge = {-1, -1}; +}; + +bool is_root_token(int token); + +QuadRoleAnalysis analyze_triangle_derived_quadrilateral( + std::span quad_tokens); + +inline int parent_triangle_edge_token(int token_a, int token_b) +{ + if (is_root_token(token_a) || is_root_token(token_b)) + { + throw std::runtime_error( + "parent_triangle_edge_token: midpoint endpoints must be original vertices"); + } + + const int a = token_a - 100; + const int b = token_b - 100; + if (a < 0 || a >= 3 || b < 0 || b >= 3) + { + throw std::runtime_error( + "parent_triangle_edge_token: invalid original-vertex token"); + } + + constexpr std::array parent_edges = {{ + {0, 1}, {1, 2}, {2, 0}, + }}; + for (std::size_t e = 0; e < parent_edges.size(); ++e) + { + const LocalEdge edge = parent_edges[e]; + if ((edge.a == a && edge.b == b) || (edge.a == b && edge.b == a)) + return static_cast(e); + } + + throw std::runtime_error("parent_triangle_edge_token: parent edge not found"); +} + +template +struct MidpointInsertionResult +{ + QuadRoleAnalysis analysis; + std::vector added_vertex_coords; + std::vector added_vertex_tokens; + std::vector added_vertex_edges; + std::vector> triangles; +}; + +template +MidpointInsertionResult split_triangle_derived_quadrilateral( + std::span quad_vertex_coords, + int coord_dim, + std::span quad_tokens, + int& next_token_base) +{ + if (coord_dim <= 0) + { + throw std::invalid_argument( + "split_triangle_derived_quadrilateral: coord_dim must be positive"); + } + if (quad_vertex_coords.size() != static_cast(4 * coord_dim)) + { + throw std::invalid_argument( + "split_triangle_derived_quadrilateral: expected 4 quadrilateral vertices in flat storage"); + } + if (quad_tokens.size() != 4) + { + throw std::invalid_argument( + "split_triangle_derived_quadrilateral: expected 4 quadrilateral tokens"); + } + + MidpointInsertionResult result; + result.analysis = analyze_triangle_derived_quadrilateral(quad_tokens); + result.added_vertex_edges.push_back(result.analysis.midpoint_edge); + const LocalEdge midpoint_edge = result.analysis.midpoint_edge; + result.added_vertex_tokens.push_back(parent_triangle_edge_token( + quad_tokens[static_cast(midpoint_edge.a)], + quad_tokens[static_cast(midpoint_edge.b)])); + (void) next_token_base; + + for (int d = 0; d < coord_dim; ++d) + { + const T xa = quad_vertex_coords[static_cast( + result.analysis.midpoint_edge.a * coord_dim + d)]; + const T xb = quad_vertex_coords[static_cast( + result.analysis.midpoint_edge.b * coord_dim + d)]; + result.added_vertex_coords.push_back(T(0.5) * (xa + xb)); + } + + auto map_canonical_local_id = [&](int canonical_id) -> int + { + if (canonical_id < 4) + return result.analysis.canonical_to_actual[static_cast(canonical_id)]; + if (canonical_id == 4) + return 4; + throw std::runtime_error( + "split_triangle_derived_quadrilateral: unsupported canonical local id"); + }; + + const std::vector all_tokens = { + quad_tokens[0], + quad_tokens[1], + quad_tokens[2], + quad_tokens[3], + result.added_vertex_tokens[0], + }; + + const std::array, 3> canonical_triangles = {{ + {2, 1, 4}, + {4, 1, 0}, + {3, 4, 0}, + }}; + + result.triangles.reserve(canonical_triangles.size()); + for (const auto& tri : canonical_triangles) + { + const std::array local_ids = { + map_canonical_local_id(tri[0]), + map_canonical_local_id(tri[1]), + map_canonical_local_id(tri[2]), + }; + + result.triangles.push_back({ + all_tokens[static_cast(local_ids[0])], + all_tokens[static_cast(local_ids[1])], + all_tokens[static_cast(local_ids[2])], + }); + } + + return result; +} + +} // namespace cutcells::cell::quad_midpoint diff --git a/cpp/src/quadrature.cpp b/cpp/src/quadrature.cpp new file mode 100644 index 0000000..3f78186 --- /dev/null +++ b/cpp/src/quadrature.cpp @@ -0,0 +1,675 @@ +// Copyright (c) 2024 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT + +#include "quadrature.h" +#include "quadrature_tables.h" +#include "cut_cell.h" +#include "triangulation.h" +#include "cell_flags.h" +#include "mapping.h" + +#include +#include +#include + +namespace cutcells::quadrature +{ + +// ============================================================================= +// Degree-1 shape functions and their Jacobians on canonical cut-subcell types. +// +// For each subcell type the canonical reference vertices are (Basix ordering): +// +// interval: v0=(0), v1=(1) +// triangle: v0=(0,0), v1=(1,0), v2=(0,1) +// tetrahedron: v0=(0,0,0), v1=(1,0,0), v2=(0,1,0), v3=(0,0,1) +// +// Degree-1 shape functions on the canonical cell: +// interval: N0=1-s, N1=s +// triangle: N0=1-s-t, N1=s, N2=t +// tetrahedron: N0=1-s-t-u, N1=s, N2=t, N3=u +// +// The mapped point at canonical coordinate (s,[t,[u]]) in an actual subcell +// with real vertices V0..Vn is: +// x = sum_i N_i(s,t,...) * Vi +// +// The Jacobian of that map is: +// J = [V1-V0, V2-V0, ...] (column-major, one column per coordinate) +// +// Note: quad, hex, prism, pyramid subcells do NOT appear in the CutCells +// output (the library always triangulates cut regions into simplices). +// We only need interval, triangle, tetrahedron. +// ============================================================================= + +namespace +{ + +// --------------------------------------------------------------------------- +// Map a batch of n canonical quadrature points to actual subcell coordinates. +// +// can_pts : n * tdim canonical reference points (flat row-major) +// verts : nv * gdim actual subcell vertex coordinates (flat row-major, +// positions in whichever space we are mapping in) +// cell_type : subcell type (determines basis) +// out_pts : n * gdim output points (flat row-major) +// +// For interval subcells embedded in higher-dimensional space the "gdim" output +// dimension follows the vertex dimension; tdim == 1 but vertices live in gdim. +// --------------------------------------------------------------------------- +template +void map_canonical_to_subcell(const T* can_pts, int n, + int tdim, + const T* verts, int gdim, + cell::type cell_type, + T* out_pts) +{ + // N_i are standard Lagrange-1 basis functions on the canonical simplex. + // J = [v1-v0 | v2-v0 | ...] (each column = vertex difference) + // + // x = v0 + J * can_pt + // + // This is identical to the affine push-forward used in mapping.cpp but + // here the "parent" is the canonical subcell itself. + + const T* v0 = verts; + + for (int q = 0; q < n; ++q) + { + const T* X = can_pts + q * tdim; + T* x = out_pts + q * gdim; + + // Start at v0 + for (int d = 0; d < gdim; ++d) + x[d] = v0[d]; + + // Add contributions from each basis function N_i = X[i-1], i >= 1 + for (int i = 1; i <= tdim; ++i) + { + const T* vi = verts + i * gdim; + for (int d = 0; d < gdim; ++d) + x[d] += X[i - 1] * (vi[d] - v0[d]); + } + } +} + +// --------------------------------------------------------------------------- +// Compute |det J| for the simplex spanned by subcell vertices. +// +// verts: nv * gdim (row-major) +// cell_type: interval(1D→1D/2D/3D), triangle(2D→2D/3D), tetrahedron(3D→3D) +// +// For embedded simplices (tdim < gdim) we compute the Gramian determinant +// sqrt(det(J^T J)). +// --------------------------------------------------------------------------- +template +T simplex_physical_volume_factor(const T* verts, int tdim, int gdim) +{ + // Build J: j[col*gdim + row] = v_{col+1} - v_0, col in [0, tdim) + T J[9] = {}; + const T* v0 = verts; + for (int col = 0; col < tdim; ++col) + { + const T* vi = verts + (col + 1) * gdim; + for (int row = 0; row < gdim; ++row) + J[col * gdim + row] = vi[row] - v0[row]; + } + + if (tdim == gdim) + { + // Square Jacobian: use direct det + if (tdim == 1) + { + return std::abs(J[0]); + } + else if (tdim == 2) + { + return std::abs(J[0] * J[3] - J[2] * J[1]); + } + else // tdim == 3 + { + const T det = + J[0] * (J[4] * J[8] - J[7] * J[5]) + - J[3] * (J[1] * J[8] - J[7] * J[2]) + + J[6] * (J[1] * J[5] - J[4] * J[2]); + return std::abs(det); + } + } + else + { + // Non-square: Gramian sqrt(det(J^T J)) + // G[i][j] = sum_k J[i*gdim+k] * J[j*gdim+k] + T G[9] = {}; + for (int i = 0; i < tdim; ++i) + for (int j = 0; j < tdim; ++j) + { + T s = 0; + for (int k = 0; k < gdim; ++k) + s += J[i * gdim + k] * J[j * gdim + k]; + G[i * tdim + j] = s; + } + + if (tdim == 1) + return std::sqrt(G[0]); + else if (tdim == 2) + return std::sqrt(G[0] * G[3] - G[1] * G[2]); + else + { + const T det = + G[0] * (G[4] * G[8] - G[7] * G[5]) + - G[3] * (G[1] * G[8] - G[7] * G[2]) + + G[6] * (G[1] * G[5] - G[4] * G[2]); + return std::sqrt(det); + } + } +} + +// --------------------------------------------------------------------------- +// Gather vertex coordinates for subcell id from either the reference or +// physical vertex array of cut_cell. +// --------------------------------------------------------------------------- +template +void gather_subcell_verts(const cutcells::cell::CutCell& cut_cell, + int subcell_id, + const std::vector& coord_array, + int gdim, + std::vector& out) +{ + const auto verts = cutcells::cell::cell_vertices(cut_cell, subcell_id); + const int nv = static_cast(verts.size()); + out.resize(static_cast(nv) * gdim); + for (int j = 0; j < nv; ++j) + for (int d = 0; d < gdim; ++d) + out[static_cast(j) * gdim + d] = + coord_array[static_cast(verts[j]) * gdim + d]; +} + +} // anonymous namespace + +// ============================================================================= +// append_quadrature +// ============================================================================= +template +void append_quadrature(const cutcells::cell::CutCell& cut_cell, + int order, + QuadratureRules& rules) +{ + const int gdim = cut_cell._gdim; + const int tdim = cut_cell._tdim; // topological dim of the parent cell + const int nc = cutcells::cell::num_cells(cut_cell); + + if (nc == 0) + return; + + // Initialise _tdim on first call + if (rules._tdim == 0) + rules._tdim = tdim; + + // Ensure offset is initialised + if (rules._offset.empty()) + rules._offset.push_back(0); + + const int32_t pts_before = static_cast(rules._weights.size()); + + // Scratch buffers + std::vector ref_verts_sub; // reference subcell vertices + std::vector phys_verts_sub; // physical subcell vertices + std::vector mapped_ref_pts; // canonical pts mapped to ref subcell + + // Helper: process one simplex sub-sub-cell (triangle or tetrahedron). + // ref_verts_sub / phys_verts_sub must already contain the nv*gdim vertex + // coordinates of the simplex. stype must be interval/triangle/tetrahedron. + auto process_simplex = [&](cutcells::cell::type stype_) + { + const auto& ref_rule = get_reference_rule(stype_, order); + const int nq = ref_rule._num_points; + const int sdim = ref_rule._tdim; // canonical simplex tdim + + // ref_verts_sub contains vertices in the parent reference space, which + // has dimension gdim (= tdim for non-embedded cells). We must output + // gdim-dimensional parent reference coordinates, not sdim-dimensional + // canonical coordinates. For interface subcells sdim < gdim (e.g. + // interval sdim=1 from a triangle parent gdim=2), using sdim as the + // output stride would only read the first component of each vertex + // and produce wrong parent-reference points for physical_points. + mapped_ref_pts.resize(static_cast(nq) * gdim); + map_canonical_to_subcell(ref_rule._points.data(), nq, sdim, + ref_verts_sub.data(), gdim, + stype_, + mapped_ref_pts.data()); + + rules._points.insert(rules._points.end(), + mapped_ref_pts.begin(), mapped_ref_pts.end()); + + const T det_phys = simplex_physical_volume_factor( + phys_verts_sub.data(), sdim, gdim); + + for (int q = 0; q < nq; ++q) + rules._weights.push_back(ref_rule._weights[q] * det_phys); + }; + + // Helper: set ref_verts_sub and phys_verts_sub from a list of local vertex + // indices into an already-filled parent buffer (ref_verts_parent / + // phys_verts_parent), each of size nv_parent * gdim. + auto set_tet_verts = [&](const std::vector& ref_parent, + const std::vector& phys_parent, + const std::vector& tet_local_ids) + { + const int nv_tet = static_cast(tet_local_ids.size()); + ref_verts_sub.resize(static_cast(nv_tet) * gdim); + phys_verts_sub.resize(static_cast(nv_tet) * gdim); + for (int j = 0; j < nv_tet; ++j) + { + const int vi = tet_local_ids[j]; + for (int d = 0; d < gdim; ++d) + { + ref_verts_sub[j * gdim + d] = ref_parent[vi * gdim + d]; + phys_verts_sub[j * gdim + d] = phys_parent[vi * gdim + d]; + } + } + }; + + for (int s = 0; s < nc; ++s) + { + const cutcells::cell::type stype = cut_cell._types[s]; + + // For non-simplex types (prism, pyramid, quadrilateral) we + // sub-triangulate into simplices to keep the integration exact. + // The sub-triangulation uses local indices into the subcell vertex list. + if (stype == cutcells::cell::type::prism || + stype == cutcells::cell::type::pyramid || + stype == cutcells::cell::type::quadrilateral) + { + // Gather all vertices of this subcell into temporary arrays. + std::vector ref_parent, phys_parent; + gather_subcell_verts(cut_cell, s, cut_cell._vertex_coords, gdim, ref_parent); + gather_subcell_verts(cut_cell, s, cut_cell._vertex_coords_phys, gdim, phys_parent); + + const int nv_sub = cutcells::cell::num_cell_vertices(cut_cell, s); + std::vector local_ids(nv_sub); + for (int j = 0; j < nv_sub; ++j) local_ids[j] = j; + + // Obtain tet/triangle decomposition using existing triangulation.h + std::vector> sub_simplices; + cutcells::cell::triangulation(stype, local_ids.data(), sub_simplices); + + // Determine simplex type based on topological dimension + const cutcells::cell::type simplex_type = + (tdim == 3) ? cutcells::cell::type::tetrahedron + : cutcells::cell::type::triangle; + + for (const auto& simplex : sub_simplices) + { + set_tet_verts(ref_parent, phys_parent, simplex); + process_simplex(simplex_type); + } + continue; + } + + // Simplex path (interval, triangle, tetrahedron): + gather_subcell_verts(cut_cell, s, cut_cell._vertex_coords, gdim, ref_verts_sub); + gather_subcell_verts(cut_cell, s, cut_cell._vertex_coords_phys, gdim, phys_verts_sub); + process_simplex(stype); + } + + // One rule entry per CutCell: record how many new points were added + rules._parent_map.push_back(cut_cell._parent_cell_index); + rules._offset.push_back(static_cast(rules._weights.size())); +} + +// ============================================================================= +// make_quadrature (in-place) +// ============================================================================= +template +void make_quadrature(const std::vector>& cut_cells, + int order, + QuadratureRules& rules) +{ + rules._tdim = 0; + rules._points.clear(); + rules._weights.clear(); + rules._offset.clear(); + rules._parent_map.clear(); + + for (std::size_t i = 0; i < cut_cells.size(); ++i) + { + append_quadrature(cut_cells[i], order, rules); + // Override the parent_map entry written by append_quadrature to store + // the position in the input vector rather than _parent_cell_index + // (which may not be set by the caller). + if (!rules._parent_map.empty()) + rules._parent_map.back() = static_cast(i); + } +} + +// ============================================================================= +// make_quadrature (returns new object) +// ============================================================================= +template +QuadratureRules make_quadrature( + const std::vector>& cut_cells, int order) +{ + QuadratureRules rules; + make_quadrature(cut_cells, order, rules); + return rules; +} + +// ============================================================================= +// runtime_quadrature +// ============================================================================= +template +QuadratureRules runtime_quadrature( + std::span ls_vals, + std::span points, + std::span connectivity, + std::span offset, + std::span vtk_type, + const std::string& cut_type_str, + bool triangulate, + int order) +{ + using namespace cutcells::cell; + + const int ncells = static_cast(vtk_type.size()); + const cut_type ctype_enum = string_to_cut_type(cut_type_str); + + // Thread-local scratch (reused across calls, avoids repeated allocations) + thread_local std::vector tl_vtx_buf; + thread_local std::vector tl_ls_buf; + thread_local std::vector tl_vtx_buf_2d; // 2D projection for embedded 2D cells + thread_local CutCell tl_scratch; + thread_local std::vector tl_can_verts; + thread_local std::vector tl_ref_sub; + thread_local std::vector tl_phys_sub; + thread_local std::vector tl_mapped_ref; + + QuadratureRules rules; + rules._offset.push_back(0); + + for (int ci = 0; ci < ncells; ++ci) + { + const int coff = offset[ci]; + const type ctype = map_vtk_type_to_cell_type( + static_cast(vtk_type[ci])); + const int nv = get_num_vertices(ctype); + const int tdim = get_tdim(ctype); + + // Gather level-set values at the cell vertices + tl_ls_buf.resize(nv); + for (int j = 0; j < nv; ++j) + tl_ls_buf[j] = ls_vals[connectivity[coff + j]]; + + const domain dom = classify_cell_domain( + std::span(tl_ls_buf.data(), nv)); + + const bool is_full = (ctype_enum == cut_type::philt0 && dom == domain::inside) || + (ctype_enum == cut_type::phigt0 && dom == domain::outside); + const bool is_cut = (ctype_enum != cut_type::unset && dom == domain::intersected); + + if (!is_full && !is_cut) + continue; + + // Latch output dimension on first contributing cell + if (rules._tdim == 0) + rules._tdim = tdim; + + // Gather physical vertex coordinates (gdim = 3, VTK convention) + tl_vtx_buf.resize(nv * 3); + for (int j = 0; j < nv; ++j) + { + const int vid = connectivity[coff + j]; + tl_vtx_buf[j * 3 + 0] = points[vid * 3 + 0]; + tl_vtx_buf[j * 3 + 1] = points[vid * 3 + 1]; + tl_vtx_buf[j * 3 + 2] = points[vid * 3 + 2]; + } + + // ------------------------------------------------------------------ + // Full-cell path + // ------------------------------------------------------------------ + if (is_full) + { + const bool is_simplex = (ctype == type::interval || + ctype == type::triangle || + ctype == type::tetrahedron); + + if (!triangulate || is_simplex) + { + // Direct rule: reference points are canonical quadrature points + const auto& ref_rule = get_reference_rule(ctype, order); + const T det_J = affine_volume_factor(ctype, tl_vtx_buf.data(), 3); + const int nq = ref_rule._num_points; + + rules._points.insert(rules._points.end(), + ref_rule._points.begin(), ref_rule._points.end()); + for (int q = 0; q < nq; ++q) + rules._weights.push_back(ref_rule._weights[q] * det_J); + } + else + { + // Triangulate into simplices; integrate over each sub-simplex + tl_can_verts = canonical_vertices(ctype); + + std::vector local_ids(nv); + for (int j = 0; j < nv; ++j) local_ids[j] = j; + + std::vector> sub_simplices; + triangulation(ctype, local_ids.data(), sub_simplices); + + const type simplex_type = + (tdim == 3) ? type::tetrahedron : type::triangle; + const int sv = (tdim == 3) ? 4 : 3; // vertices per simplex + + const auto& ref_rule_s = get_reference_rule(simplex_type, order); + const int nq = ref_rule_s._num_points; + tl_mapped_ref.resize(nq * tdim); + + for (const auto& simplex : sub_simplices) + { + // Gather ref-space vertices of this sub-simplex (tdim-D coords) + tl_ref_sub.resize(sv * tdim); + for (int k = 0; k < sv; ++k) + { + const int vi = simplex[k]; + for (int d = 0; d < tdim; ++d) + tl_ref_sub[k * tdim + d] = tl_can_verts[vi * tdim + d]; + } + + // Gather physical vertices of this sub-simplex (3-D coords) + tl_phys_sub.resize(sv * 3); + for (int k = 0; k < sv; ++k) + { + const int vi = simplex[k]; + for (int d = 0; d < 3; ++d) + tl_phys_sub[k * 3 + d] = tl_vtx_buf[vi * 3 + d]; + } + + // Map canonical simplex quadrature points → parent ref space + map_canonical_to_subcell( + ref_rule_s._points.data(), nq, + tdim, + tl_ref_sub.data(), tdim, + simplex_type, + tl_mapped_ref.data()); + + // Physical volume factor for this sub-simplex + const T det = simplex_physical_volume_factor( + tl_phys_sub.data(), tdim, 3); + + rules._points.insert(rules._points.end(), + tl_mapped_ref.begin(), tl_mapped_ref.end()); + for (int q = 0; q < nq; ++q) + rules._weights.push_back(ref_rule_s._weights[q] * det); + } + } + + rules._parent_map.push_back(static_cast(ci)); + rules._offset.push_back(static_cast(rules._weights.size())); + } + // ------------------------------------------------------------------ + // Cut-cell path + // ------------------------------------------------------------------ + else // is_cut + { + if (tdim == 1) + continue; // embedded 1D edges cannot be cut + + if (tdim == 2) + { + // For 2D cells embedded in a 3D VTK mesh (z-plane), project + // vertices to 2D (x,y) so the cutter and pull-back work in a + // square (2×2) Jacobian system. + tl_vtx_buf_2d.resize(nv * 2); + for (int j = 0; j < nv; ++j) + { + tl_vtx_buf_2d[j * 2 + 0] = tl_vtx_buf[j * 3 + 0]; + tl_vtx_buf_2d[j * 2 + 1] = tl_vtx_buf[j * 3 + 1]; + } + cell::cut(ctype, + std::span(tl_vtx_buf_2d.data(), nv * 2), + 2, + std::span(tl_ls_buf.data(), nv), + cut_type_str, + tl_scratch, + triangulate); + if (cell::num_cells(tl_scratch) == 0) + continue; + tl_scratch._parent_cell_type = ctype; + tl_scratch._parent_vertex_coords = tl_vtx_buf_2d; // 2D parent + cell::complete_from_physical(tl_scratch); + append_quadrature(tl_scratch, order, rules); + } + else // tdim == 3 + { + cell::cut(ctype, + std::span(tl_vtx_buf.data(), nv * 3), + 3, + std::span(tl_ls_buf.data(), nv), + cut_type_str, + tl_scratch, + triangulate); + if (cell::num_cells(tl_scratch) == 0) + continue; + tl_scratch._parent_cell_type = ctype; + tl_scratch._parent_vertex_coords = tl_vtx_buf; + cell::complete_from_physical(tl_scratch); + append_quadrature(tl_scratch, order, rules); + } + + // append_quadrature records tl_scratch._parent_cell_index; override + // with the global mesh index + if (!rules._parent_map.empty()) + rules._parent_map.back() = static_cast(ci); + } + } + + return rules; +} + +// ============================================================================= +// physical_points +// ============================================================================= +template +std::vector physical_points( + const QuadratureRules& rules, + std::span points, + std::span connectivity, + std::span offset, + std::span vtk_type) +{ + using namespace cutcells::cell; + + const int nrules = static_cast(rules._parent_map.size()); + const int total_pts = static_cast(rules._weights.size()); + const int tdim = rules._tdim; + + std::vector out(static_cast(total_pts) * 3, T(0)); + + if (nrules == 0 || total_pts == 0 || tdim == 0) + return out; + + thread_local std::vector tl_phys_verts; + + for (int i = 0; i < nrules; ++i) + { + const int q_begin = rules._offset[i]; + const int q_end = rules._offset[i + 1]; + const int nq = q_end - q_begin; + if (nq == 0) + continue; + + const int ci = rules._parent_map[i]; + const int coff = offset[ci]; + const type ctype = map_vtk_type_to_cell_type( + static_cast(vtk_type[ci])); + if (get_tdim(ctype) != tdim) + throw std::invalid_argument( + "physical_points: quadrature point dimension does not match " + "the supplied VTK parent cell type"); + const int nv = get_num_vertices(ctype); + + // Gather physical vertices for this cell + tl_phys_verts.resize(nv * 3); + for (int j = 0; j < nv; ++j) + { + const int vid = connectivity[coff + j]; + tl_phys_verts[j * 3 + 0] = points[vid * 3 + 0]; + tl_phys_verts[j * 3 + 1] = points[vid * 3 + 1]; + tl_phys_verts[j * 3 + 2] = points[vid * 3 + 2]; + } + + // Push parent reference points to physical coordinates with the same + // affine column convention used by the cut-cell maps. + { + const auto cols = jacobian_col_indices(ctype); + const T* v0 = tl_phys_verts.data(); + const T* Xref = rules._points.data() + q_begin * tdim; + T* xout = out.data() + q_begin * 3; + for (int q = 0; q < nq; ++q) + { + for (int d = 0; d < 3; ++d) + xout[q * 3 + d] = v0[d]; + for (int k = 0; k < tdim; ++k) + { + const T* vk = tl_phys_verts.data() + cols[k] * 3; + const T xi_k = Xref[q * tdim + k]; + for (int d = 0; d < 3; ++d) + xout[q * 3 + d] += (vk[d] - v0[d]) * xi_k; + } + } + } + } + + return out; +} + +// ============================================================================= +// Explicit instantiations +// ============================================================================= +template void append_quadrature(const cutcells::cell::CutCell&, int, QuadratureRules&); +template void append_quadrature(const cutcells::cell::CutCell&, int, QuadratureRules&); + +template void make_quadrature(const std::vector>&, int, QuadratureRules&); +template void make_quadrature(const std::vector>&, int, QuadratureRules&); + +template QuadratureRules make_quadrature(const std::vector>&, int); +template QuadratureRules make_quadrature(const std::vector>&, int); + +template QuadratureRules runtime_quadrature( + std::span, std::span, + std::span, std::span, std::span, + const std::string&, bool, int); +template QuadratureRules runtime_quadrature( + std::span, std::span, + std::span, std::span, std::span, + const std::string&, bool, int); + +template std::vector physical_points( + const QuadratureRules&, + std::span, std::span, std::span, std::span); +template std::vector physical_points( + const QuadratureRules&, + std::span, std::span, std::span, std::span); + +} // namespace cutcells::quadrature diff --git a/cpp/src/quadrature.h b/cpp/src/quadrature.h new file mode 100644 index 0000000..7e49768 --- /dev/null +++ b/cpp/src/quadrature.h @@ -0,0 +1,146 @@ +// Copyright (c) 2024 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "cut_cell.h" +#include "cell_types.h" + +#include +#include +#include +#include +#include + +namespace cutcells::quadrature +{ + +/// Flat batch of quadrature rules for a collection of cut cells. +/// +/// Conventions: +/// - _points are in the **parent reference element** (dimension _tdim each). +/// - _weights are **physical integration weights** (|det J_phys| scaled). +/// - Rule i covers parent cell _parent_map[i]. +/// - Points for rule i live at _points[_offset[i]*_tdim .. _offset[i+1]*_tdim). +/// - Weights for rule i live at _weights[_offset[i] .. _offset[i+1]). +/// +/// Preconditions before calling make_quadrature / append_quadrature: +/// - cut_cell._vertex_coords must hold parent **reference** coordinates. +/// - cut_cell._vertex_coords_phys must hold **physical** coordinates. +/// - Both are filled after calling compute_physical_cut_vertices() or +/// complete_from_physical() from mapping.h. +template +struct QuadratureRules +{ + /// Topological dimension of the parent reference element (= dim of each point) + int _tdim = 0; + + /// Flat quadrature points in parent reference space. + /// Size: total_num_points * _tdim + std::vector _points; + + /// Physical integration weights. + /// Size: total_num_points + std::vector _weights; + + /// CSR offsets: rule i has points in [_offset[i], _offset[i+1]). + /// Size: num_rules + 1 (_offset[0] = 0 always) + std::vector _offset; + + /// Parent cell index for each rule. + /// Size: num_rules + std::vector _parent_map; +}; + +/// Append the quadrature contribution of one cut cell. +/// +/// Iterates over the subcells of cut_cell, fetches the canonical rule for +/// each subcell type at the given polynomial order, maps canonical points to +/// the parent reference element (using _vertex_coords), computes physical +/// weights (using _vertex_coords_phys), and appends everything to rules. +/// +/// @param cut_cell enriched cut cell (both coord frames must be set) +/// @param order polynomial order for the canonical quadrature rule +/// @param rules output accumulator (appended to, not reset) +template +void append_quadrature(const cutcells::cell::CutCell& cut_cell, + int order, + QuadratureRules& rules); + +/// Build QuadratureRules for a vector of cut cells. +/// rules._offset and rules._parent_map are built alongside the point/weight data. +/// @param cut_cells enriched cut-cell vector +/// @param order polynomial order +/// @param rules output (reset before use) +template +void make_quadrature(const std::vector>& cut_cells, + int order, + QuadratureRules& rules); + +/// Convenience overload that returns a freshly constructed QuadratureRules. +template +QuadratureRules make_quadrature( + const std::vector>& cut_cells, int order); + +/// @brief Generate flat quadrature rules for all mesh cells matching the +/// requested level-set domain. +/// +/// Iterates over all cells in the VTK-format mesh. For each cell the level-set +/// domain is classified: +/// - *Full cells* (entirely inside / outside): a standard reference-space +/// rule is used, scaled by |det J_phys|. When @p triangulate is true and +/// the cell is not a simplex, the cell is first decomposed into simplices. +/// - *Cut cells* (intersected): the cell is cut, the reference/physical +/// coordinates are completed via mapping.h, and append_quadrature is called. +/// +/// All input arrays are flat and follow VTK conventions: +/// @p points npts × 3 (gdim = 3 always for VTK meshes) +/// @p connectivity concatenated vertex-index lists for all cells +/// @p offset CSR offsets: cell i uses connectivity[offset[i]..offset[i+1]) +/// @p vtk_type VTK element-type code per cell +/// +/// @param ls_vals level-set values at all mesh vertices (size = npts) +/// @param points flat mesh vertex coordinates (size = npts * 3) +/// @param connectivity flat vertex connectivity list +/// @param offset CSR offsets (size = ncells + 1) +/// @param vtk_type VTK cell types (size = ncells) +/// @param cut_type_str one of "phi<0", "phi>0", "phi=0" +/// @param triangulate if true, non-simplex cells are split into simplices +/// @param order polynomial quadrature order +/// @returns flat QuadratureRules in parent reference space +template +QuadratureRules runtime_quadrature( + std::span ls_vals, + std::span points, + std::span connectivity, + std::span offset, + std::span vtk_type, + const std::string& cut_type_str, + bool triangulate, + int order); + +/// @brief Push reference-space quadrature points to physical space. +/// +/// For each rule i in @p rules, the reference-space points stored in +/// rules._points are mapped to physical space using the affine pushforward +/// of parent cell @c rules._parent_map[i]. +/// +/// @param rules quadrature rules (output of runtime_quadrature or +/// make_quadrature) +/// @param points flat mesh vertex coordinates (size = npts * 3) +/// @param connectivity flat vertex connectivity list +/// @param offset CSR offsets (size = ncells + 1) +/// @param vtk_type VTK cell types (size = ncells) +/// @returns flat physical point coordinates +/// (total_num_points * 3) +template +std::vector physical_points( + const QuadratureRules& rules, + std::span points, + std::span connectivity, + std::span offset, + std::span vtk_type); + +} // namespace cutcells::quadrature diff --git a/cpp/src/quadrature_tables.h b/cpp/src/quadrature_tables.h new file mode 100644 index 0000000..d86d02b --- /dev/null +++ b/cpp/src/quadrature_tables.h @@ -0,0 +1,125 @@ +// Copyright (c) 2024 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +// --------------------------------------------------------------------------- +// Thin lookup interface over Basix-generated quadrature tables. +// Generated headers live in cpp/src/generated/quadrature_tables_.h and +// are produced by tablegen/scripts/gen_quadrature_tables.py. +// --------------------------------------------------------------------------- + +#include "cell_types.h" + +#include "generated/quadrature_tables_interval.h" +#include "generated/quadrature_tables_triangle.h" +#include "generated/quadrature_tables_quadrilateral.h" +#include "generated/quadrature_tables_tetrahedron.h" +#include "generated/quadrature_tables_hexahedron.h" +#include "generated/quadrature_tables_prism.h" +#include "generated/quadrature_tables_pyramid.h" + +#include +#include +#include +#include + +namespace cutcells::quadrature +{ + +/// Reference quadrature rule for given cell type and polynomial order. +/// Points are in the Basix canonical reference element. +/// Weights are the canonical integration weights (sum = reference cell volume). +/// +/// The struct owns its data via std::vector. The template parameter T +/// is the requested floating-point precision; data is stored as double and +/// converted on construction. +template +struct ReferenceQuadratureRule +{ + int _tdim = 0; ///< topological dimension of the canonical cell + int _num_points = 0; ///< number of quadrature points + std::vector _points; ///< flat: _num_points * _tdim + std::vector _weights; ///< flat: _num_points +}; + +// --------------------------------------------------------------------------- +// Internal helper – copy double arrays to vector +// --------------------------------------------------------------------------- +namespace detail +{ + +template +ReferenceQuadratureRule make_rule(int tdim, int npts, + const double* pts_d, + const double* wts_d) +{ + ReferenceQuadratureRule r; + r._tdim = tdim; + r._num_points = npts; + r._points.resize(static_cast(npts) * tdim); + r._weights.resize(static_cast(npts)); + for (int i = 0; i < npts * tdim; ++i) + r._points[i] = static_cast(pts_d[i]); + for (int i = 0; i < npts; ++i) + r._weights[i] = static_cast(wts_d[i]); + return r; +} + +} // namespace detail + +// --------------------------------------------------------------------------- +// Public lookup function +// --------------------------------------------------------------------------- + +/// Return the canonical quadrature rule for (cell_type, order). +/// Supports cell types: interval, triangle, quadrilateral, tetrahedron, +/// hexahedron, prism, pyramid. +/// Supports orders 1..10 (as generated by gen_quadrature_tables.py). +/// Throws std::invalid_argument for unsupported combinations. +template +ReferenceQuadratureRule get_reference_rule(cell::type cell_type, int order) +{ + using namespace cutcells::quadrature::generated; + +#define CUTCELLS_DISPATCH_CELL(CNAME, CTYPE) \ + case cell::type::CTYPE: \ + switch (order) { \ + case 1: return detail::make_rule(CNAME##_o1_tdim, CNAME##_o1_npts, CNAME##_o1_points, CNAME##_o1_weights); \ + case 2: return detail::make_rule(CNAME##_o2_tdim, CNAME##_o2_npts, CNAME##_o2_points, CNAME##_o2_weights); \ + case 3: return detail::make_rule(CNAME##_o3_tdim, CNAME##_o3_npts, CNAME##_o3_points, CNAME##_o3_weights); \ + case 4: return detail::make_rule(CNAME##_o4_tdim, CNAME##_o4_npts, CNAME##_o4_points, CNAME##_o4_weights); \ + case 5: return detail::make_rule(CNAME##_o5_tdim, CNAME##_o5_npts, CNAME##_o5_points, CNAME##_o5_weights); \ + case 6: return detail::make_rule(CNAME##_o6_tdim, CNAME##_o6_npts, CNAME##_o6_points, CNAME##_o6_weights); \ + case 7: return detail::make_rule(CNAME##_o7_tdim, CNAME##_o7_npts, CNAME##_o7_points, CNAME##_o7_weights); \ + case 8: return detail::make_rule(CNAME##_o8_tdim, CNAME##_o8_npts, CNAME##_o8_points, CNAME##_o8_weights); \ + case 9: return detail::make_rule(CNAME##_o9_tdim, CNAME##_o9_npts, CNAME##_o9_points, CNAME##_o9_weights); \ + case 10: return detail::make_rule(CNAME##_o10_tdim, CNAME##_o10_npts, CNAME##_o10_points, CNAME##_o10_weights); \ + default: break; \ + } \ + break + + switch (cell_type) + { + CUTCELLS_DISPATCH_CELL(interval, interval); + CUTCELLS_DISPATCH_CELL(triangle, triangle); + CUTCELLS_DISPATCH_CELL(quadrilateral, quadrilateral); + CUTCELLS_DISPATCH_CELL(tetrahedron, tetrahedron); + CUTCELLS_DISPATCH_CELL(hexahedron, hexahedron); + CUTCELLS_DISPATCH_CELL(prism, prism); + CUTCELLS_DISPATCH_CELL(pyramid, pyramid); + default: + throw std::invalid_argument( + "get_reference_rule: unsupported cell type"); + } + +#undef CUTCELLS_DISPATCH_CELL + + throw std::invalid_argument( + "get_reference_rule: order " + std::to_string(order) + + " not available (max order is 10; regenerate tables with higher max_order)"); +} + +} // namespace cutcells::quadrature diff --git a/cpp/src/reference_cell.h b/cpp/src/reference_cell.h new file mode 100644 index 0000000..13e32e6 --- /dev/null +++ b/cpp/src/reference_cell.h @@ -0,0 +1,270 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "cell_types.h" + +#include +#include +#include +#include +#include + +namespace cutcells::cell +{ + +template +inline std::vector reference_vertices(type cell_type) +{ + switch (cell_type) + { + case type::interval: + return {T(0), T(1)}; + case type::triangle: + return {T(0), T(0), T(1), T(0), T(0), T(1)}; + case type::quadrilateral: + return {T(0), T(0), T(1), T(0), T(0), T(1), T(1), T(1)}; + case type::tetrahedron: + return {T(0), T(0), T(0), + T(1), T(0), T(0), + T(0), T(1), T(0), + T(0), T(0), T(1)}; + case type::hexahedron: + return {T(0), T(0), T(0), + T(1), T(0), T(0), + T(0), T(1), T(0), + T(1), T(1), T(0), + T(0), T(0), T(1), + T(1), T(0), T(1), + T(0), T(1), T(1), + T(1), T(1), T(1)}; + case type::prism: + return {T(0), T(0), T(0), + T(1), T(0), T(0), + T(0), T(1), T(0), + T(0), T(0), T(1), + T(1), T(0), T(1), + T(0), T(1), T(1)}; + case type::pyramid: + return {T(0), T(0), T(0), + T(1), T(0), T(0), + T(0), T(1), T(0), + T(1), T(1), T(0), + T(0), T(0), T(1)}; + default: + throw std::invalid_argument("reference_vertices: unsupported cell type"); + } +} + +inline std::span vtk_to_basix_vertex_permutation(type cell_type) +{ + static constexpr std::array interval = {0, 1}; + static constexpr std::array triangle = {0, 1, 2}; + static constexpr std::array quadrilateral = {0, 1, 3, 2}; + static constexpr std::array tetrahedron = {0, 1, 2, 3}; + static constexpr std::array hexahedron = {0, 1, 3, 2, 4, 5, 7, 6}; + static constexpr std::array prism = {0, 1, 2, 3, 4, 5}; + static constexpr std::array pyramid = {0, 1, 3, 2, 4}; + + switch (cell_type) + { + case type::interval: return std::span(interval); + case type::triangle: return std::span(triangle); + case type::quadrilateral: return std::span(quadrilateral); + case type::tetrahedron: return std::span(tetrahedron); + case type::hexahedron: return std::span(hexahedron); + case type::prism: return std::span(prism); + case type::pyramid: return std::span(pyramid); + default: + throw std::invalid_argument("vtk_to_basix_vertex_permutation: unsupported cell type"); + } +} + +inline std::span basix_to_vtk_vertex_permutation(type cell_type) +{ + // For the supported first-order cells the inverse happens to equal the forward map. + return vtk_to_basix_vertex_permutation(cell_type); +} + +inline int vtk_to_basix_vertex(type cell_type, int vtk_vertex) +{ + const auto perm = vtk_to_basix_vertex_permutation(cell_type); + if (vtk_vertex < 0 || vtk_vertex >= static_cast(perm.size())) + throw std::invalid_argument("vtk_to_basix_vertex: vertex id out of bounds"); + + for (std::size_t i = 0; i < perm.size(); ++i) + { + if (perm[i] == vtk_vertex) + return static_cast(i); + } + + throw std::invalid_argument("vtk_to_basix_vertex: invalid vtk vertex id"); +} + +inline int basix_to_vtk_vertex(type cell_type, int basix_vertex) +{ + const auto perm = basix_to_vtk_vertex_permutation(cell_type); + if (basix_vertex < 0 || basix_vertex >= static_cast(perm.size())) + throw std::invalid_argument("basix_to_vtk_vertex: vertex id out of bounds"); + return perm[static_cast(basix_vertex)]; +} + +inline std::span vtk_to_basix_edge_permutation(type cell_type) +{ + static constexpr std::array interval = {0}; + static constexpr std::array triangle = {2, 0, 1}; + static constexpr std::array quadrilateral = {0, 2, 3, 1}; + static constexpr std::array tetrahedron = {5, 2, 4, 3, 1, 0}; + static constexpr std::array hexahedron = {0, 3, 1, 2, 4, 7, 5, 6, 8, 9, 11, 10}; + static constexpr std::array prism = {0, 2, 1, 6, 8, 7, 3, 4, 5}; + static constexpr std::array pyramid = {0, 2, 3, 1, 4, 5, 6, 7}; + + switch (cell_type) + { + case type::interval: return std::span(interval); + case type::triangle: return std::span(triangle); + case type::quadrilateral: return std::span(quadrilateral); + case type::tetrahedron: return std::span(tetrahedron); + case type::hexahedron: return std::span(hexahedron); + case type::prism: return std::span(prism); + case type::pyramid: return std::span(pyramid); + default: + throw std::invalid_argument("vtk_to_basix_edge_permutation: unsupported cell type"); + } +} + +inline int vtk_to_basix_edge(type cell_type, int vtk_edge) +{ + const auto perm = vtk_to_basix_edge_permutation(cell_type); + if (vtk_edge < 0 || vtk_edge >= static_cast(perm.size())) + throw std::invalid_argument("vtk_to_basix_edge: edge id out of bounds"); + return perm[static_cast(vtk_edge)]; +} + +inline int basix_to_vtk_edge(type cell_type, int basix_edge) +{ + const auto perm = vtk_to_basix_edge_permutation(cell_type); + if (basix_edge < 0 || basix_edge >= static_cast(perm.size())) + throw std::invalid_argument("basix_to_vtk_edge: edge id out of bounds"); + + for (std::size_t i = 0; i < perm.size(); ++i) + { + if (perm[i] == basix_edge) + return static_cast(i); + } + + throw std::invalid_argument("basix_to_vtk_edge: invalid basix edge id"); +} + +inline int vtk_parent_entity_token_to_basix(type cell_type, int token) +{ + if (token < 0) + return token; + if (token < 100) + return vtk_to_basix_edge(cell_type, token); + if (token < 200) + return 100 + vtk_to_basix_vertex(cell_type, token - 100); + return token; +} + +template +inline std::vector permute_vertex_data(std::span data, int ncomp, + std::span permutation) +{ + if (ncomp < 0) + throw std::invalid_argument("permute_vertex_data: negative component count"); + if (data.size() != static_cast(ncomp) * permutation.size()) + throw std::invalid_argument("permute_vertex_data: data size does not match permutation"); + + std::vector out(data.size()); + for (std::size_t i = 0; i < permutation.size(); ++i) + { + const int src = permutation[i]; + if (src < 0 || src >= static_cast(permutation.size())) + throw std::invalid_argument("permute_vertex_data: permutation entry out of bounds"); + + for (int c = 0; c < ncomp; ++c) + { + out[i * static_cast(ncomp) + static_cast(c)] + = data[static_cast(src) * static_cast(ncomp) + + static_cast(c)]; + } + } + return out; +} + +template +inline std::vector permute_vertex_ids(std::span ids, + std::span permutation) +{ + if (ids.size() != permutation.size()) + throw std::invalid_argument("permute_vertex_ids: size does not match permutation"); + + std::vector out(ids.size()); + for (std::size_t i = 0; i < permutation.size(); ++i) + { + const int src = permutation[i]; + if (src < 0 || src >= static_cast(permutation.size())) + throw std::invalid_argument("permute_vertex_ids: permutation entry out of bounds"); + out[i] = ids[static_cast(src)]; + } + return out; +} + +template +inline void reorder_subcell_vertices_from_vtk_to_basix(type cell_type, + std::array& vertices, + int nvertices) +{ + const auto perm = vtk_to_basix_vertex_permutation(cell_type); + if (nvertices != static_cast(perm.size())) + return; + + const auto reordered = permute_vertex_ids( + std::span(vertices.data(), static_cast(nvertices)), perm); + for (int i = 0; i < nvertices; ++i) + vertices[static_cast(i)] = reordered[static_cast(i)]; +} + +template +inline std::array remap_token_to_vertex_map_from_vtk_to_basix( + type cell_type, const std::array& vtk_token_to_vertex, + int n_edges, int n_vertices, int n_special = 0) +{ + std::array basix_token_to_vertex; + basix_token_to_vertex.fill(-1); + + for (int token = 0; token < n_edges; ++token) + { + if (vtk_token_to_vertex[static_cast(token)] >= 0) + { + basix_token_to_vertex[static_cast( + vtk_parent_entity_token_to_basix(cell_type, token))] + = vtk_token_to_vertex[static_cast(token)]; + } + } + + for (int token = 100; token < 100 + n_vertices; ++token) + { + if (vtk_token_to_vertex[static_cast(token)] >= 0) + { + basix_token_to_vertex[static_cast( + vtk_parent_entity_token_to_basix(cell_type, token))] + = vtk_token_to_vertex[static_cast(token)]; + } + } + + for (int token = 200; token < 200 + n_special; ++token) + { + if (vtk_token_to_vertex[static_cast(token)] >= 0) + basix_token_to_vertex[static_cast(token)] + = vtk_token_to_vertex[static_cast(token)]; + } + + return basix_token_to_vertex; +} + +} // namespace cutcells::cell diff --git a/cpp/src/refine_cell.cpp b/cpp/src/refine_cell.cpp new file mode 100644 index 0000000..81b4926 --- /dev/null +++ b/cpp/src/refine_cell.cpp @@ -0,0 +1,1135 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "refine_cell.h" +#include "cell_subdivision.h" +#include "cell_topology.h" + +#include +#include +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +template +struct EdgeStateData +{ + std::array verts = {0, 0}; + std::vector tags; + std::vector split_params; + std::vector split_has_value; + std::vector root_params; + std::vector root_vertex_ids; + std::vector root_has_value; +}; + +template +struct CapturedEdgeState +{ + int num_level_sets = 0; + std::vector, EdgeStateData>> edges_in_old_order; + std::map, const EdgeStateData*> by_key; +}; + +template +CapturedEdgeState capture_edge_state(const AdaptCell& adapt_cell) +{ + CapturedEdgeState state; + const int n_edges = adapt_cell.n_entities(1); + state.num_level_sets = adapt_cell.edge_root_tag_num_level_sets; + state.edges_in_old_order.reserve(static_cast(n_edges)); + + for (int e = 0; e < n_edges; ++e) + { + auto ev = adapt_cell.entity_to_vertex[1][static_cast(e)]; + const std::pair key = { + std::min(static_cast(ev[0]), static_cast(ev[1])), + std::max(static_cast(ev[0]), static_cast(ev[1]))}; + + EdgeStateData data; + data.verts = {ev[0], ev[1]}; + data.tags.resize(static_cast(state.num_level_sets), + EdgeRootTag::not_classified); + data.split_params.resize(static_cast(state.num_level_sets), T(0)); + data.split_has_value.resize(static_cast(state.num_level_sets), + std::uint8_t(0)); + data.root_params.resize(static_cast(state.num_level_sets), T(0)); + data.root_vertex_ids.resize(static_cast(state.num_level_sets), + std::int32_t(-1)); + data.root_has_value.resize(static_cast(state.num_level_sets), + std::uint8_t(0)); + + for (int ls = 0; ls < state.num_level_sets; ++ls) + { + data.tags[static_cast(ls)] = + adapt_cell.get_edge_root_tag(ls, e); + + const auto idx = static_cast(ls * n_edges + e); + if (idx < adapt_cell.edge_green_split_param.size()) + data.split_params[static_cast(ls)] = + adapt_cell.edge_green_split_param[idx]; + if (idx < adapt_cell.edge_green_split_has_value.size()) + data.split_has_value[static_cast(ls)] = + adapt_cell.edge_green_split_has_value[idx]; + if (idx < adapt_cell.edge_one_root_param.size()) + data.root_params[static_cast(ls)] = + adapt_cell.edge_one_root_param[idx]; + if (idx < adapt_cell.edge_one_root_vertex_id.size()) + data.root_vertex_ids[static_cast(ls)] = + adapt_cell.edge_one_root_vertex_id[idx]; + if (idx < adapt_cell.edge_one_root_has_value.size()) + data.root_has_value[static_cast(ls)] = + adapt_cell.edge_one_root_has_value[idx]; + } + + state.edges_in_old_order.emplace_back(key, std::move(data)); + } + + for (auto& [key, data] : state.edges_in_old_order) + state.by_key[key] = &data; + + return state; +} + +template +void clear_topology_caches(AdaptCell& adapt_cell) +{ + for (auto& row : adapt_cell.connectivity) + { + for (auto& conn : row) + { + conn.offsets.clear(); + conn.indices.clear(); + } + } + adapt_cell.has_connectivity = {}; + + adapt_cell.zero_entity_dim.clear(); + adapt_cell.zero_entity_id.clear(); + adapt_cell.zero_entity_zero_mask.clear(); + adapt_cell.zero_entity_is_owned.clear(); + adapt_cell.zero_entity_parent_dim.clear(); + adapt_cell.zero_entity_parent_id.clear(); + adapt_cell.zero_entity_host_cell_id.clear(); + adapt_cell.zero_entity_host_cell_type.clear(); + adapt_cell.zero_entity_host_face_id.clear(); + adapt_cell.zero_entity_source_level_set.clear(); + adapt_cell.zero_entity_host_cell_vertices.offsets.clear(); + adapt_cell.zero_entity_host_cell_vertices.indices.clear(); + ++adapt_cell.zero_entity_version; +} + +template +int append_vertex_with_parent_info(AdaptCell& adapt_cell, + std::span coords, + int parent_dim, + int parent_id, + std::span parent_param, + int source_edge_id) +{ + const int new_v = adapt_cell.n_vertices(); + adapt_cell.vertex_coords.insert(adapt_cell.vertex_coords.end(), + coords.begin(), coords.end()); + adapt_cell.vertex_parent_dim.push_back(static_cast(parent_dim)); + adapt_cell.vertex_parent_id.push_back(static_cast(parent_id)); + const std::int32_t param_offset = + static_cast(adapt_cell.vertex_parent_param.size()); + adapt_cell.vertex_parent_param.insert(adapt_cell.vertex_parent_param.end(), + parent_param.begin(), + parent_param.end()); + adapt_cell.vertex_parent_param_offset.push_back( + param_offset + static_cast(parent_param.size())); + adapt_cell.zero_mask_per_vertex.push_back(0); + adapt_cell.negative_mask_per_vertex.push_back(0); + adapt_cell.vertex_source_edge_id.push_back(static_cast(source_edge_id)); + return new_v; +} + +template +bool vertex_parameter_on_parent_edge(const AdaptCell& adapt_cell, + int vertex_id, + int parent_edge_id, + T& t) +{ + const int dim = adapt_cell.vertex_parent_dim[static_cast(vertex_id)]; + if (dim == 0) + { + const auto parent_edge = + cell::edges(adapt_cell.parent_cell_type)[static_cast(parent_edge_id)]; + if (parent_edge[0] == vertex_id) + { + t = T(0); + return true; + } + if (parent_edge[1] == vertex_id) + { + t = T(1); + return true; + } + return false; + } + + if (dim != 1) + return false; + if (adapt_cell.vertex_parent_id[static_cast(vertex_id)] != parent_edge_id) + return false; + + const auto begin = + static_cast(adapt_cell.vertex_parent_param_offset[static_cast(vertex_id)]); + const auto end = + static_cast(adapt_cell.vertex_parent_param_offset[static_cast(vertex_id + 1)]); + if (end - begin != 1) + return false; + + t = adapt_cell.vertex_parent_param[begin]; + return true; +} + +template +bool edge_is_on_single_parent_edge(const AdaptCell& adapt_cell, + int edge_id, + int& parent_edge_id) +{ + auto edge_vertices = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + if (edge_vertices.size() != 2) + return false; + + const auto parent_edges = cell::edges(adapt_cell.parent_cell_type); + for (int pe = 0; pe < static_cast(parent_edges.size()); ++pe) + { + T unused = T(0); + if (vertex_parameter_on_parent_edge(adapt_cell, edge_vertices[0], pe, unused) + && vertex_parameter_on_parent_edge(adapt_cell, edge_vertices[1], pe, unused)) + { + parent_edge_id = pe; + return true; + } + } + + return false; +} + +template +int append_interpolated_vertex_on_edge(AdaptCell& adapt_cell, int edge_id, T t) +{ + auto edge_vertices = adapt_cell.entity_to_vertex[1][static_cast(edge_id)]; + if (edge_vertices.size() != 2) + throw std::runtime_error("append_interpolated_vertex_on_edge: invalid edge"); + const int v0 = edge_vertices[0]; + const int v1 = edge_vertices[1]; + + std::vector x(static_cast(adapt_cell.tdim), T(0)); + for (int d = 0; d < adapt_cell.tdim; ++d) + { + const T x0 = adapt_cell.vertex_coords[static_cast(v0 * adapt_cell.tdim + d)]; + const T x1 = adapt_cell.vertex_coords[static_cast(v1 * adapt_cell.tdim + d)]; + x[static_cast(d)] = (T(1) - t) * x0 + t * x1; + } + + int parent_dim = adapt_cell.tdim; + int parent_id = adapt_cell.parent_cell_id; + std::vector parent_param(x.begin(), x.end()); + + int parent_edge_id = -1; + T parent_t0 = T(0); + T parent_t1 = T(1); + if (edge_is_on_single_parent_edge(adapt_cell, edge_id, parent_edge_id) + && vertex_parameter_on_parent_edge(adapt_cell, v0, parent_edge_id, parent_t0) + && vertex_parameter_on_parent_edge(adapt_cell, v1, parent_edge_id, parent_t1)) + { + parent_dim = 1; + parent_id = parent_edge_id; + parent_param.assign(1, (T(1) - t) * parent_t0 + t * parent_t1); + } + + return append_vertex_with_parent_info( + adapt_cell, + std::span(x), + parent_dim, + parent_id, + std::span(parent_param), + edge_id); +} + +template +int append_cell_center_vertex(AdaptCell& adapt_cell, std::span verts) +{ + std::vector x(static_cast(adapt_cell.tdim), T(0)); + const T inv = T(1) / T(static_cast(verts.size())); + for (auto v : verts) + { + for (int d = 0; d < adapt_cell.tdim; ++d) + x[static_cast(d)] += + adapt_cell.vertex_coords[static_cast(v * adapt_cell.tdim + d)] * inv; + } + return append_vertex_with_parent_info( + adapt_cell, + std::span(x), + adapt_cell.tdim, + adapt_cell.parent_cell_id, + std::span(x), + -1); +} + +template +std::map, int> build_edge_lookup(const AdaptCell& adapt_cell) +{ + std::map, int> edge_lookup; + const int n_edges = adapt_cell.n_entities(1); + for (int e = 0; e < n_edges; ++e) + { + auto ev = adapt_cell.entity_to_vertex[1][static_cast(e)]; + const int a = std::min(static_cast(ev[0]), static_cast(ev[1])); + const int b = std::max(static_cast(ev[0]), static_cast(ev[1])); + edge_lookup[{a, b}] = e; + } + return edge_lookup; +} + +template +void rebuild_leaf_edges_preserve_certification( + AdaptCell& adapt_cell, + const CapturedEdgeState& old_edge_state) +{ + const int tdim = adapt_cell.tdim; + std::map, std::array> new_leaf_edges; + std::vector> new_edge_keys_in_encounter_order; + + const int n_cells = adapt_cell.n_entities(tdim); + for (int c = 0; c < n_cells; ++c) + { + const cell::type ctype = adapt_cell.entity_types[tdim][static_cast(c)]; + auto cell_verts = adapt_cell.entity_to_vertex[tdim][static_cast(c)]; + auto cell_edges = cell::edges(ctype); + + for (const auto& ce : cell_edges) + { + const std::int32_t lv0 = cell_verts[static_cast(ce[0])]; + const std::int32_t lv1 = cell_verts[static_cast(ce[1])]; + const std::pair key = { + std::min(static_cast(lv0), static_cast(lv1)), + std::max(static_cast(lv0), static_cast(lv1))}; + + if (!new_leaf_edges.contains(key)) + { + new_leaf_edges[key] = {lv0, lv1}; + new_edge_keys_in_encounter_order.push_back(key); + } + } + } + + std::vector> final_edges; + std::vector*> final_old_state; + final_edges.reserve(new_leaf_edges.size()); + final_old_state.reserve(new_leaf_edges.size()); + + for (const auto& [key, old_data] : old_edge_state.edges_in_old_order) + { + auto it = new_leaf_edges.find(key); + if (it == new_leaf_edges.end()) + continue; + + final_edges.push_back(old_data.verts); + final_old_state.push_back(&old_data); + new_leaf_edges.erase(it); + } + + for (const auto& key : new_edge_keys_in_encounter_order) + { + auto it = new_leaf_edges.find(key); + if (it == new_leaf_edges.end()) + continue; + + final_edges.push_back(it->second); + final_old_state.push_back(nullptr); + } + + adapt_cell.entity_types[1].assign(final_edges.size(), cell::type::interval); + adapt_cell.entity_to_vertex[1].indices.clear(); + adapt_cell.entity_to_vertex[1].offsets.clear(); + adapt_cell.entity_to_vertex[1].offsets.push_back(0); + for (const auto& edge : final_edges) + { + adapt_cell.entity_to_vertex[1].indices.push_back(edge[0]); + adapt_cell.entity_to_vertex[1].indices.push_back(edge[1]); + adapt_cell.entity_to_vertex[1].offsets.push_back( + static_cast(adapt_cell.entity_to_vertex[1].indices.size())); + } + + const int n_edges = static_cast(final_edges.size()); + const int nls = old_edge_state.num_level_sets; + adapt_cell.edge_root_tag_num_level_sets = nls; + adapt_cell.edge_root_tag.assign(static_cast(nls * n_edges), + EdgeRootTag::not_classified); + adapt_cell.edge_green_split_param.assign(static_cast(nls * n_edges), T(0)); + adapt_cell.edge_green_split_has_value.assign(static_cast(nls * n_edges), + std::uint8_t(0)); + adapt_cell.edge_one_root_param.assign(static_cast(nls * n_edges), T(0)); + adapt_cell.edge_one_root_vertex_id.assign(static_cast(nls * n_edges), + std::int32_t(-1)); + adapt_cell.edge_one_root_has_value.assign(static_cast(nls * n_edges), + std::uint8_t(0)); + + for (int e = 0; e < n_edges; ++e) + { + const auto* old_data = final_old_state[static_cast(e)]; + if (!old_data) + continue; + + for (int ls = 0; ls < nls; ++ls) + { + const auto idx = static_cast(ls * n_edges + e); + adapt_cell.edge_root_tag[idx] = old_data->tags[static_cast(ls)]; + adapt_cell.edge_green_split_param[idx] = + old_data->split_params[static_cast(ls)]; + adapt_cell.edge_green_split_has_value[idx] = + old_data->split_has_value[static_cast(ls)]; + adapt_cell.edge_one_root_param[idx] = + old_data->root_params[static_cast(ls)]; + adapt_cell.edge_one_root_vertex_id[idx] = + old_data->root_vertex_ids[static_cast(ls)]; + adapt_cell.edge_one_root_has_value[idx] = + old_data->root_has_value[static_cast(ls)]; + } + } +} + +template +void rebuild_leaf_cell_certification( + AdaptCell& adapt_cell, + std::span old_cell_tags, + int old_num_level_sets, + int old_num_cells, + std::span old_cell_ids_for_new_cells) +{ + const int new_num_cells = adapt_cell.n_entities(adapt_cell.tdim); + adapt_cell.cell_cert_tag_num_level_sets = old_num_level_sets; + adapt_cell.cell_cert_tag.assign( + static_cast(old_num_level_sets * new_num_cells), + CellCertTag::not_classified); + + for (int c = 0; c < new_num_cells; ++c) + { + const int old_c = old_cell_ids_for_new_cells[static_cast(c)]; + if (old_c < 0 || old_c >= old_num_cells) + continue; + + for (int ls = 0; ls < old_num_level_sets; ++ls) + { + adapt_cell.cell_cert_tag[static_cast(ls * new_num_cells + c)] = + old_cell_tags[static_cast(ls * old_num_cells + old_c)]; + } + } +} + +template +void rebuild_leaf_cell_provenance( + AdaptCell& adapt_cell, + std::span old_cell_types, + const EntityAdjacency& old_cells, + std::span old_cell_source_cell_id, + std::span old_cell_refinement_generation, + std::span old_cell_refinement_reason, + std::span old_cell_host_parent_cell_id, + const EntityAdjacency& old_host_cell_vertices, + std::span old_host_cell_types, + std::span old_source_level_sets, + std::span old_cell_ids_for_new_cells, + std::span source_cell_ids_for_new_cells, + std::span refinement_reasons_for_new_cells) +{ + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + + adapt_cell.cell_source_cell_id.assign( + static_cast(n_cells), std::int32_t(-1)); + adapt_cell.cell_refinement_generation.assign( + static_cast(n_cells), std::int32_t(0)); + adapt_cell.cell_refinement_reason.assign( + static_cast(n_cells), CellRefinementReason::none); + adapt_cell.cell_host_parent_cell_id.assign( + static_cast(n_cells), adapt_cell.parent_cell_id); + + adapt_cell.entity_host_cell_id[tdim].clear(); + adapt_cell.entity_host_cell_type[tdim].clear(); + adapt_cell.entity_host_face_id[tdim].clear(); + adapt_cell.entity_source_level_set[tdim].clear(); + adapt_cell.entity_host_cell_vertices[tdim].offsets.clear(); + adapt_cell.entity_host_cell_vertices[tdim].indices.clear(); + adapt_cell.entity_host_cell_vertices[tdim].offsets.push_back(std::int32_t(0)); + + auto append_host_vertices = [&](std::span vertices) + { + for (const auto v : vertices) + adapt_cell.entity_host_cell_vertices[tdim].indices.push_back(v); + adapt_cell.entity_host_cell_vertices[tdim].offsets.push_back( + static_cast( + adapt_cell.entity_host_cell_vertices[tdim].indices.size())); + }; + + const int old_num_cells = static_cast(old_cell_types.size()); + for (int c = 0; c < n_cells; ++c) + { + const int old_c = + (c < static_cast(old_cell_ids_for_new_cells.size())) + ? old_cell_ids_for_new_cells[static_cast(c)] + : -1; + const int source_c = + (c < static_cast(source_cell_ids_for_new_cells.size())) + ? source_cell_ids_for_new_cells[static_cast(c)] + : old_c; + const CellRefinementReason reason = + (c < static_cast(refinement_reasons_for_new_cells.size())) + ? refinement_reasons_for_new_cells[static_cast(c)] + : CellRefinementReason::none; + const CellRefinementReason stored_reason = + (reason == CellRefinementReason::none + && old_c >= 0 + && old_c < static_cast(old_cell_refinement_reason.size())) + ? old_cell_refinement_reason[static_cast(old_c)] + : reason; + const int meta_c = + (source_c >= 0 && source_c < old_num_cells) + ? source_c + : ((old_c >= 0 && old_c < old_num_cells) ? old_c : -1); + + adapt_cell.cell_source_cell_id[static_cast(c)] = + static_cast(source_c); + if (meta_c >= 0 + && meta_c < static_cast(old_cell_refinement_generation.size())) + { + const bool unchanged_copy = + old_c == source_c && old_c >= 0 + && reason == CellRefinementReason::none; + adapt_cell.cell_refinement_generation[static_cast(c)] = + old_cell_refinement_generation[static_cast(meta_c)] + + (unchanged_copy ? 0 : 1); + } + adapt_cell.cell_refinement_reason[static_cast(c)] = stored_reason; + if (meta_c >= 0 + && meta_c < static_cast(old_cell_host_parent_cell_id.size())) + { + adapt_cell.cell_host_parent_cell_id[static_cast(c)] = + old_cell_host_parent_cell_id[static_cast(meta_c)]; + } + + adapt_cell.entity_host_cell_id[tdim].push_back( + static_cast(source_c)); + cell::type host_type = + adapt_cell.entity_types[tdim][static_cast(c)]; + if (meta_c >= 0 + && meta_c < static_cast(old_host_cell_types.size())) + { + host_type = old_host_cell_types[static_cast(meta_c)]; + } + else if (meta_c >= 0 + && meta_c < static_cast(old_cell_types.size())) + { + host_type = old_cell_types[static_cast(meta_c)]; + } + adapt_cell.entity_host_cell_type[tdim].push_back(host_type); + adapt_cell.entity_host_face_id[tdim].push_back(std::int32_t(-1)); + const int source_level_set = + (meta_c >= 0 && meta_c < static_cast(old_source_level_sets.size())) + ? old_source_level_sets[static_cast(meta_c)] + : -1; + adapt_cell.entity_source_level_set[tdim].push_back( + reason == CellRefinementReason::cut_level_set + ? source_level_set + : std::int32_t(-1)); + + std::span host_vertices; + if (reason == CellRefinementReason::cut_level_set + && meta_c >= 0 && meta_c < old_cells.size()) + { + host_vertices = old_cells[static_cast(meta_c)]; + } + else if (old_c >= 0 && old_c < old_host_cell_vertices.size() + && reason == CellRefinementReason::none) + { + host_vertices = old_host_cell_vertices[static_cast(old_c)]; + } + if (host_vertices.empty()) + host_vertices = adapt_cell.entity_to_vertex[tdim][static_cast(c)]; + append_host_vertices(host_vertices); + } +} + +void append_top_cell(std::vector& types, + EntityAdjacency& adj, + cell::type ctype, + std::span verts) +{ + types.push_back(ctype); + for (int v : verts) + adj.indices.push_back(static_cast(v)); + adj.offsets.push_back(static_cast(adj.indices.size())); +} + +} // anonymous namespace + +template +void apply_topology_update_preserve_certification( + AdaptCell& adapt_cell, + std::vector&& new_types, + EntityAdjacency&& new_cells, + std::span old_cell_ids_for_new_cells, + std::span source_cell_ids_for_new_cells, + std::span refinement_reasons_for_new_cells) +{ + const int tdim = adapt_cell.tdim; + const CapturedEdgeState old_edge_state = capture_edge_state(adapt_cell); + const int old_num_level_sets = adapt_cell.cell_cert_tag_num_level_sets; + const int old_num_cells = adapt_cell.n_entities(tdim); + const std::vector old_cell_tags = adapt_cell.cell_cert_tag; + const std::vector old_cell_types = adapt_cell.entity_types[tdim]; + const EntityAdjacency old_cells = adapt_cell.entity_to_vertex[tdim]; + const std::vector old_cell_source_cell_id = + adapt_cell.cell_source_cell_id; + const std::vector old_cell_refinement_generation = + adapt_cell.cell_refinement_generation; + const std::vector old_cell_refinement_reason = + adapt_cell.cell_refinement_reason; + const std::vector old_cell_host_parent_cell_id = + adapt_cell.cell_host_parent_cell_id; + const EntityAdjacency old_host_cell_vertices = + adapt_cell.entity_host_cell_vertices[tdim]; + const std::vector old_host_cell_types = + adapt_cell.entity_host_cell_type[tdim]; + const std::vector old_source_level_sets = + adapt_cell.entity_source_level_set[tdim]; + + adapt_cell.entity_types[tdim] = std::move(new_types); + adapt_cell.entity_to_vertex[tdim] = std::move(new_cells); + + rebuild_leaf_cell_provenance( + adapt_cell, + std::span(old_cell_types), + old_cells, + std::span(old_cell_source_cell_id), + std::span(old_cell_refinement_generation), + std::span(old_cell_refinement_reason), + std::span(old_cell_host_parent_cell_id), + old_host_cell_vertices, + std::span(old_host_cell_types), + std::span(old_source_level_sets), + old_cell_ids_for_new_cells, + source_cell_ids_for_new_cells, + refinement_reasons_for_new_cells); + rebuild_leaf_edges_preserve_certification(adapt_cell, old_edge_state); + rebuild_leaf_cell_certification(adapt_cell, + std::span(old_cell_tags), + old_num_level_sets, + old_num_cells, + old_cell_ids_for_new_cells); + clear_topology_caches(adapt_cell); + + const int nls = std::max(adapt_cell.cell_cert_tag_num_level_sets, + adapt_cell.edge_root_tag_num_level_sets); + recompute_active_level_set_masks(adapt_cell, nls); + rebuild_zero_entity_inventory(adapt_cell); +} + +// ===================================================================== +// Invalidation helpers +// ===================================================================== + +template +void invalidate_edge_tags_for_new_edges(AdaptCell& adapt_cell, + std::span new_edge_ids) +{ + const int nls = adapt_cell.edge_root_tag_num_level_sets; + const int n_edges = adapt_cell.n_entities(1); + for (int ls = 0; ls < nls; ++ls) + { + for (int e : new_edge_ids) + { + if (e >= 0 && e < n_edges) + adapt_cell.set_edge_root_tag(ls, e, EdgeRootTag::not_classified); + } + } +} + +template +void invalidate_cell_tags_for_new_cells(AdaptCell& adapt_cell, + std::span new_cell_ids) +{ + const int nls = adapt_cell.cell_cert_tag_num_level_sets; + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + for (int ls = 0; ls < nls; ++ls) + { + for (int c : new_cell_ids) + { + if (c >= 0 && c < n_cells) + adapt_cell.set_cell_cert_tag(ls, c, CellCertTag::not_classified); + } + } +} + +// ===================================================================== +// Green refinement +// ===================================================================== + +template +bool refine_green_on_multiple_root_edges(AdaptCell& adapt_cell, + int level_set_id) +{ + if (adapt_cell.tdim != 2 && adapt_cell.tdim != 3) + return false; + + const int n_edges = adapt_cell.n_entities(1); + int split_edge = -1; + T split_t = T(0); + for (int e = 0; e < n_edges; ++e) + { + if (adapt_cell.get_edge_root_tag(level_set_id, e) != EdgeRootTag::multiple_roots) + continue; + const auto idx = static_cast(level_set_id * n_edges + e); + if (!adapt_cell.edge_green_split_has_value[idx]) + continue; + split_edge = e; + split_t = adapt_cell.edge_green_split_param[idx]; + break; + } + + if (split_edge < 0) + return false; + + const int tdim = adapt_cell.tdim; + const std::vector old_types = adapt_cell.entity_types[tdim]; + const EntityAdjacency old_cells = adapt_cell.entity_to_vertex[tdim]; + + if (tdim == 2 + && std::any_of(old_types.begin(), old_types.end(), + [](cell::type ct) { return ct != cell::type::triangle; })) + { + return false; + } + if (tdim == 3 + && std::any_of(old_types.begin(), old_types.end(), + [](cell::type ct) { return ct != cell::type::tetrahedron; })) + { + return false; + } + + auto ev = adapt_cell.entity_to_vertex[1][static_cast(split_edge)]; + const int v0 = ev[0]; + const int v1 = ev[1]; + const int new_v = append_interpolated_vertex_on_edge(adapt_cell, split_edge, split_t); + + EntityAdjacency new_cells; + new_cells.offsets.push_back(0); + std::vector new_types; + std::vector old_cell_ids_for_new_cells; + std::vector source_cell_ids_for_new_cells; + std::vector refinement_reasons_for_new_cells; + + for (int c = 0; c < static_cast(old_types.size()); ++c) + { + auto verts = old_cells[static_cast(c)]; + + if (tdim == 2) + { + const int a = verts[0]; + const int b = verts[1]; + const int cvert = verts[2]; + bool split = false; + + if ((a == v0 && b == v1) || (a == v1 && b == v0)) + { + const std::array t0 = {a, new_v, cvert}; + const std::array t1 = {new_v, b, cvert}; + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t0)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t1)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + split = true; + } + else if ((b == v0 && cvert == v1) || (b == v1 && cvert == v0)) + { + const std::array t0 = {b, new_v, a}; + const std::array t1 = {new_v, cvert, a}; + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t0)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t1)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + split = true; + } + else if ((cvert == v0 && a == v1) || (cvert == v1 && a == v0)) + { + const std::array t0 = {cvert, new_v, b}; + const std::array t1 = {new_v, a, b}; + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t0)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(t1)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + split = true; + } + + if (!split) + { + std::vector copy(verts.begin(), verts.end()); + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(copy)); + old_cell_ids_for_new_cells.push_back(c); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::none); + } + } + else + { + const int a = verts[0]; + const int b = verts[1]; + const int cvert = verts[2]; + const int dvert = verts[3]; + bool split = false; + + const std::array, 6> edge_positions = {{ + {{0, 1, 2, 3}}, + {{0, 2, 1, 3}}, + {{0, 3, 1, 2}}, + {{1, 2, 0, 3}}, + {{1, 3, 0, 2}}, + {{2, 3, 0, 1}}, + }}; + + const std::array tet = {a, b, cvert, dvert}; + for (const auto& pos : edge_positions) + { + const int e0 = tet[static_cast(pos[0])]; + const int e1 = tet[static_cast(pos[1])]; + if (!((e0 == v0 && e1 == v1) || (e0 == v1 && e1 == v0))) + continue; + + // Preserve orientation: replace one endpoint at a time + // in the parent's vertex ordering. + std::array t0 = tet; + t0[static_cast(pos[1])] = new_v; + std::array t1 = tet; + t1[static_cast(pos[0])] = new_v; + append_top_cell(new_types, new_cells, cell::type::tetrahedron, std::span(t0)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + append_top_cell(new_types, new_cells, cell::type::tetrahedron, std::span(t1)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::green_edge); + split = true; + break; + } + + if (!split) + { + std::vector copy(verts.begin(), verts.end()); + append_top_cell(new_types, new_cells, cell::type::tetrahedron, std::span(copy)); + old_cell_ids_for_new_cells.push_back(c); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::none); + } + } + } + + apply_topology_update_preserve_certification( + adapt_cell, std::move(new_types), std::move(new_cells), + std::span(old_cell_ids_for_new_cells), + std::span(source_cell_ids_for_new_cells), + std::span(refinement_reasons_for_new_cells)); + return true; +} + +// ===================================================================== +// Red refinement +// ===================================================================== + +template +bool refine_red_on_ambiguous_cells(AdaptCell& adapt_cell, + int level_set_id) +{ + const int tdim = adapt_cell.tdim; + const int n_cells = adapt_cell.n_entities(tdim); + + std::vector ambiguous_cells; + for (int c = 0; c < n_cells; ++c) + { + if (adapt_cell.get_cell_cert_tag(level_set_id, c) + == CellCertTag::ambiguous) + ambiguous_cells.push_back(c); + } + + if (ambiguous_cells.empty()) + return false; + + const std::vector old_types = adapt_cell.entity_types[tdim]; + const EntityAdjacency old_cells = adapt_cell.entity_to_vertex[tdim]; + const auto edge_lookup = build_edge_lookup(adapt_cell); + + std::map midpoint_vertex_by_edge; + auto get_midpoint_vertex = [&](int edge_id) -> int + { + auto it = midpoint_vertex_by_edge.find(edge_id); + if (it != midpoint_vertex_by_edge.end()) + return it->second; + + const int mid = append_interpolated_vertex_on_edge(adapt_cell, edge_id, T(0.5)); + midpoint_vertex_by_edge[edge_id] = mid; + return mid; + }; + + EntityAdjacency new_cells; + new_cells.offsets.push_back(0); + std::vector new_types; + std::vector old_cell_ids_for_new_cells; + std::vector source_cell_ids_for_new_cells; + std::vector refinement_reasons_for_new_cells; + + for (int c = 0; c < n_cells; ++c) + { + auto verts = old_cells[static_cast(c)]; + const cell::type ctype = old_types[static_cast(c)]; + const bool is_ambiguous = + (adapt_cell.get_cell_cert_tag(level_set_id, c) == CellCertTag::ambiguous); + + if (!is_ambiguous) + { + std::vector copy(verts.begin(), verts.end()); + append_top_cell(new_types, new_cells, ctype, std::span(copy)); + old_cell_ids_for_new_cells.push_back(c); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::none); + continue; + } + + switch (ctype) + { + case cell::type::interval: + { + const int a = verts[0]; + const int b = verts[1]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto it = edge_lookup.find(key); + if (it == edge_lookup.end()) + { + throw std::runtime_error( + "refine_red_on_ambiguous_cells: missing interval edge key=(" + + std::to_string(key.first) + "," + std::to_string(key.second) + + ") for cell " + std::to_string(c)); + } + const int e = it->second; + const int m = get_midpoint_vertex(e); + const std::array, 2> children = {{{a, m}, {m, b}}}; + for (const auto& child : children) + { + append_top_cell(new_types, new_cells, cell::type::interval, std::span(child)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::red_cell); + } + break; + } + case cell::type::triangle: + { + std::array local_to_global = { + static_cast(verts[0]), static_cast(verts[1]), static_cast(verts[2]), + -1, -1, -1}; + const auto ledges = cell::edges(cell::type::triangle); + for (int le = 0; le < 3; ++le) + { + const int a = verts[static_cast(ledges[static_cast(le)][0])]; + const int b = verts[static_cast(ledges[static_cast(le)][1])]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto it = edge_lookup.find(key); + if (it == edge_lookup.end()) + { + throw std::runtime_error( + "refine_red_on_ambiguous_cells: missing triangle edge key=(" + + std::to_string(key.first) + "," + std::to_string(key.second) + + ") for cell " + std::to_string(c) + " local_edge=" + + std::to_string(le)); + } + local_to_global[static_cast(3 + le)] = + get_midpoint_vertex(it->second); + } + + for (const auto& child : cell::triangle_subdivision_table) + { + const std::array g = { + local_to_global[static_cast(child[0])], + local_to_global[static_cast(child[1])], + local_to_global[static_cast(child[2])]}; + append_top_cell(new_types, new_cells, cell::type::triangle, std::span(g)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::red_cell); + } + break; + } + case cell::type::quadrilateral: + { + std::array local_to_global = { + static_cast(verts[0]), static_cast(verts[1]), + static_cast(verts[2]), static_cast(verts[3]), + -1, -1, -1, -1, -1}; + const auto ledges = cell::edges(cell::type::quadrilateral); + for (int le = 0; le < 4; ++le) + { + const int a = verts[static_cast(ledges[static_cast(le)][0])]; + const int b = verts[static_cast(ledges[static_cast(le)][1])]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto it = edge_lookup.find(key); + if (it == edge_lookup.end()) + { + throw std::runtime_error( + "refine_red_on_ambiguous_cells: missing quadrilateral edge key=(" + + std::to_string(key.first) + "," + std::to_string(key.second) + + ") for cell " + std::to_string(c) + " local_edge=" + + std::to_string(le)); + } + local_to_global[static_cast(4 + le)] = + get_midpoint_vertex(it->second); + } + local_to_global[8] = append_cell_center_vertex(adapt_cell, verts); + + for (const auto& child : cell::quadrilateral_subdivision_table) + { + const std::array g = { + local_to_global[static_cast(child[0])], + local_to_global[static_cast(child[1])], + local_to_global[static_cast(child[2])], + local_to_global[static_cast(child[3])]}; + append_top_cell(new_types, new_cells, cell::type::quadrilateral, std::span(g)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::red_cell); + } + break; + } + case cell::type::tetrahedron: + { + std::array local_to_global = { + static_cast(verts[0]), static_cast(verts[1]), + static_cast(verts[2]), static_cast(verts[3]), + -1, -1, -1, -1, -1, -1}; + const auto ledges = cell::edges(cell::type::tetrahedron); + for (int le = 0; le < 6; ++le) + { + const int a = verts[static_cast(ledges[static_cast(le)][0])]; + const int b = verts[static_cast(ledges[static_cast(le)][1])]; + const std::pair key = {std::min(a, b), std::max(a, b)}; + auto it = edge_lookup.find(key); + if (it == edge_lookup.end()) + { + throw std::runtime_error( + "refine_red_on_ambiguous_cells: missing tetrahedron edge key=(" + + std::to_string(key.first) + "," + std::to_string(key.second) + + ") for cell " + std::to_string(c) + " local_edge=" + + std::to_string(le)); + } + local_to_global[static_cast(4 + le)] = + get_midpoint_vertex(it->second); + } + + for (const auto& child : cell::tetrahedron_subdivision_table) + { + const std::array g = { + local_to_global[static_cast(child[0])], + local_to_global[static_cast(child[1])], + local_to_global[static_cast(child[2])], + local_to_global[static_cast(child[3])]}; + append_top_cell(new_types, new_cells, cell::type::tetrahedron, std::span(g)); + old_cell_ids_for_new_cells.push_back(-1); + source_cell_ids_for_new_cells.push_back(c); + refinement_reasons_for_new_cells.push_back(CellRefinementReason::red_cell); + } + break; + } + default: + throw std::runtime_error( + "refine_red_on_ambiguous_cells: unsupported cell type"); + } + } + + apply_topology_update_preserve_certification( + adapt_cell, std::move(new_types), std::move(new_cells), + std::span(old_cell_ids_for_new_cells), + std::span(source_cell_ids_for_new_cells), + std::span(refinement_reasons_for_new_cells)); + return true; +} + +// ===================================================================== +// Explicit template instantiations +// ===================================================================== + +template void invalidate_edge_tags_for_new_edges(AdaptCell&, + std::span); +template void invalidate_edge_tags_for_new_edges(AdaptCell&, + std::span); + +template void invalidate_cell_tags_for_new_cells(AdaptCell&, + std::span); +template void invalidate_cell_tags_for_new_cells(AdaptCell&, + std::span); + +template +void invalidate_face_tags_for_new_faces(AdaptCell& adapt_cell, + std::span new_face_ids) +{ + const int nls = adapt_cell.face_cert_tag_num_level_sets; + const int n_faces = adapt_cell.n_entities(2); + for (int ls = 0; ls < nls; ++ls) + { + for (int f : new_face_ids) + { + if (f >= 0 && f < n_faces) + adapt_cell.set_face_cert_tag(ls, f, FaceCertTag::not_classified); + } + } +} + +template void invalidate_face_tags_for_new_faces(AdaptCell&, + std::span); +template void invalidate_face_tags_for_new_faces(AdaptCell&, + std::span); + +template bool refine_green_on_multiple_root_edges(AdaptCell&, int); +template bool refine_green_on_multiple_root_edges(AdaptCell&, int); + +template bool refine_red_on_ambiguous_cells(AdaptCell&, int); +template bool refine_red_on_ambiguous_cells(AdaptCell&, int); + +template void apply_topology_update_preserve_certification( + AdaptCell&, + std::vector&&, + EntityAdjacency&&, + std::span, + std::span, + std::span); +template void apply_topology_update_preserve_certification( + AdaptCell&, + std::vector&&, + EntityAdjacency&&, + std::span, + std::span, + std::span); + +} // namespace cutcells diff --git a/cpp/src/refine_cell.h b/cpp/src/refine_cell.h new file mode 100644 index 0000000..816b5cc --- /dev/null +++ b/cpp/src/refine_cell.h @@ -0,0 +1,90 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +#include "adapt_cell.h" + +namespace cutcells +{ + +// ===================================================================== +// Green refinement — split multiple-roots edges +// ===================================================================== + +/// Perform green refinement on cells incident to an edge tagged multiple_roots. +/// +/// Each call refines the first multiple_roots edge with a stored green_split_t. +/// Repeated calls, or certify_and_refine(...), therefore resolve multiple such +/// edges recursively. +/// +/// For the selected multiple_roots edge: +/// 1. Insert a new vertex on the edge at parameter t. +/// 2. Split all cells incident to that edge by connecting the new vertex. +/// 3. Replace the old edge with two child edges. +/// 4. New edges and new cells get tag not_classified. +/// 5. Unaffected edges retain their tags. +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param level_set_id Which level set. +/// @return true if any refinement was performed. +template +bool refine_green_on_multiple_root_edges(AdaptCell& adapt_cell, + int level_set_id); + +// ===================================================================== +// Red refinement — subdivide ambiguous cells +// ===================================================================== + +/// Perform red (full) refinement on cells tagged ambiguous. +/// +/// Each ambiguous cell is subdivided uniformly: edge midpoints become +/// new vertices and the cell is replaced by 2^tdim children (or the +/// appropriate simplex subdivision). +/// +/// @param adapt_cell The AdaptCell (modified in place). +/// @param level_set_id Which level set. +/// @return true if any refinement was performed. +template +bool refine_red_on_ambiguous_cells(AdaptCell& adapt_cell, + int level_set_id); + +/// Replace the current leaf-cell pool and rebuild the leaf-edge pool while +/// preserving certification state on surviving leaf entities. New leaf edges +/// and cells are left not_classified. +template +void apply_topology_update_preserve_certification( + AdaptCell& adapt_cell, + std::vector&& new_types, + EntityAdjacency&& new_cells, + std::span old_cell_ids_for_new_cells, + std::span source_cell_ids_for_new_cells = {}, + std::span refinement_reasons_for_new_cells = {}); + +// ===================================================================== +// Invalidation helpers +// ===================================================================== + +/// Reset edge tags to not_classified for a set of edge ids. +template +void invalidate_edge_tags_for_new_edges(AdaptCell& adapt_cell, + std::span new_edge_ids); + +/// Reset cell tags to not_classified for a set of cell ids. +template +void invalidate_cell_tags_for_new_cells(AdaptCell& adapt_cell, + std::span new_cell_ids); + +/// Reset face tags to not_classified for a set of face ids (3D cells). +template +void invalidate_face_tags_for_new_faces(AdaptCell& adapt_cell, + std::span new_face_ids); + +} // namespace cutcells diff --git a/cpp/src/selection_expr.cpp b/cpp/src/selection_expr.cpp new file mode 100644 index 0000000..b6dc2eb --- /dev/null +++ b/cpp/src/selection_expr.cpp @@ -0,0 +1,290 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#include "selection_expr.h" + +#include +#include +#include + +namespace cutcells +{ +namespace +{ + +/// Trim leading and trailing whitespace from a string_view. +std::string_view trim(std::string_view s) +{ + while (!s.empty() && std::isspace(static_cast(s.front()))) + s.remove_prefix(1); + while (!s.empty() && std::isspace(static_cast(s.back()))) + s.remove_suffix(1); + return s; +} + +/// Parse a single clause: "name < 0", "name > 0", or "name = 0". +Clause parse_clause(std::string_view text) +{ + text = trim(text); + if (text.empty()) + throw std::runtime_error("parse_selection_expr: empty clause"); + + // Find the relation operator: '<', '>', or '=' + auto pos_lt = text.find('<'); + auto pos_gt = text.find('>'); + auto pos_eq = text.find('='); + + std::size_t op_pos = std::string_view::npos; + Relation rel{}; + + if (pos_lt != std::string_view::npos + && (pos_lt < pos_gt || pos_gt == std::string_view::npos) + && (pos_lt < pos_eq || pos_eq == std::string_view::npos)) + { + op_pos = pos_lt; + rel = Relation::LessThan; + } + else if (pos_gt != std::string_view::npos + && (pos_gt < pos_eq || pos_eq == std::string_view::npos)) + { + op_pos = pos_gt; + rel = Relation::GreaterThan; + } + else if (pos_eq != std::string_view::npos) + { + op_pos = pos_eq; + rel = Relation::EqualTo; + } + else + { + throw std::runtime_error( + "parse_selection_expr: no operator (<, >, =) in clause: '" + + std::string(text) + "'"); + } + + std::string_view name_part = trim(text.substr(0, op_pos)); + std::string_view rhs_part = trim(text.substr(op_pos + 1)); + + if (name_part.empty()) + throw std::runtime_error( + "parse_selection_expr: missing level-set name in clause: '" + + std::string(text) + "'"); + + if (rhs_part != "0") + throw std::runtime_error( + "parse_selection_expr: right-hand side must be '0', got '" + + std::string(rhs_part) + "' in clause: '" + std::string(text) + "'"); + + Clause c; + c.name = std::string(name_part); + c.relation = rel; + return c; +} + +/// Case-insensitive check for a space-delimited keyword between clauses/terms. +/// Returns the position of the leading whitespace before the keyword, or npos. +std::size_t find_keyword_delimiter(std::string_view text, + std::string_view keyword, + std::size_t start = 0) +{ + for (std::size_t i = start; i + keyword.size() + 1 < text.size(); ++i) + { + if (!std::isspace(static_cast(text[i]))) + continue; + + bool match = true; + for (std::size_t k = 0; k < keyword.size(); ++k) + { + const char a = static_cast( + std::tolower(static_cast(text[i + 1 + k]))); + const char b = static_cast( + std::tolower(static_cast(keyword[k]))); + if (a != b) + { + match = false; + break; + } + } + + if (match + && std::isspace(static_cast( + text[i + 1 + keyword.size()]))) + { + return i; + } + } + return std::string_view::npos; +} + +SelectionTerm parse_term(std::string_view text) +{ + text = trim(text); + if (text.empty()) + throw std::runtime_error("parse_selection_expr: empty term"); + + SelectionTerm term; + std::size_t pos = 0; + while (pos < text.size()) + { + const std::size_t and_pos = find_keyword_delimiter(text, "and", pos); + std::string_view clause_text; + if (and_pos == std::string_view::npos) + { + clause_text = text.substr(pos); + pos = text.size(); + } + else + { + clause_text = text.substr(pos, and_pos - pos); + pos = and_pos + 5; // skip " and " + } + + term.clauses.push_back(parse_clause(clause_text)); + } + + if (term.clauses.empty()) + throw std::runtime_error("parse_selection_expr: no clauses found in term"); + return term; +} + +void mirror_first_term(SelectionExpr& expr) +{ + if (expr.terms.empty()) + { + expr.clauses.clear(); + expr.zero_required = 0; + expr.negative_required = 0; + expr.positive_required = 0; + return; + } + + expr.clauses = expr.terms.front().clauses; + expr.zero_required = expr.terms.front().zero_required; + expr.negative_required = expr.terms.front().negative_required; + expr.positive_required = expr.terms.front().positive_required; +} + +} // anonymous namespace + +// --------------------------------------------------------------------------- +// parse_selection_expr +// --------------------------------------------------------------------------- + +SelectionExpr parse_selection_expr(std::string_view text) +{ + text = trim(text); + if (text.empty()) + throw std::runtime_error("parse_selection_expr: empty expression"); + + SelectionExpr expr; + + std::size_t pos = 0; + while (pos < text.size()) + { + const std::size_t or_pos = find_keyword_delimiter(text, "or", pos); + std::string_view term_text; + if (or_pos == std::string_view::npos) + { + term_text = text.substr(pos); + pos = text.size(); + } + else + { + term_text = text.substr(pos, or_pos - pos); + pos = or_pos + 4; // skip " or " + } + + expr.terms.push_back(parse_term(term_text)); + } + + if (expr.terms.empty()) + throw std::runtime_error("parse_selection_expr: no terms found"); + + mirror_first_term(expr); + + return expr; +} + +// --------------------------------------------------------------------------- +// compile_selection_expr +// --------------------------------------------------------------------------- + +void compile_selection_expr(SelectionExpr& expr, + const std::vector& level_set_names) +{ + for (auto& term : expr.terms) + { + term.zero_required = 0; + term.negative_required = 0; + term.positive_required = 0; + + for (auto& clause : term.clauses) + { + auto it = std::find(level_set_names.begin(), level_set_names.end(), + clause.name); + if (it == level_set_names.end()) + { + throw std::runtime_error( + "compile_selection_expr: unknown level-set name '" + + clause.name + "'"); + } + + int idx = static_cast(std::distance(level_set_names.begin(), it)); + if (idx >= 64) + throw std::runtime_error( + "compile_selection_expr: level-set index >= 64 not supported"); + + clause.level_set_index = idx; + const std::uint64_t bit = std::uint64_t(1) << idx; + + switch (clause.relation) + { + case Relation::LessThan: + term.negative_required |= bit; + break; + case Relation::GreaterThan: + term.positive_required |= bit; + break; + case Relation::EqualTo: + term.zero_required |= bit; + break; + } + } + } + + mirror_first_term(expr); +} + +// --------------------------------------------------------------------------- +// infer_selection_dim +// --------------------------------------------------------------------------- + +int infer_selection_dim(const SelectionExpr& expr, int tdim) +{ + if (expr.terms.empty()) + throw std::runtime_error("infer_selection_dim: empty selection expression"); + + auto infer_term_dim = [tdim](const SelectionTerm& term) + { + int n_eq = 0; + for (const auto& clause : term.clauses) + if (clause.relation == Relation::EqualTo) + ++n_eq; + return std::max(0, tdim - n_eq); + }; + + const int dim = infer_term_dim(expr.terms.front()); + for (std::size_t i = 1; i < expr.terms.size(); ++i) + { + if (infer_term_dim(expr.terms[i]) != dim) + { + throw std::runtime_error( + "infer_selection_dim: 'or' terms must select the same entity dimension"); + } + } + return dim; +} + +} // namespace cutcells diff --git a/cpp/src/selection_expr.h b/cpp/src/selection_expr.h new file mode 100644 index 0000000..fc4f966 --- /dev/null +++ b/cpp/src/selection_expr.h @@ -0,0 +1,97 @@ +// Copyright (c) 2026 ONERA +// Authors: Susanne Claus +// This file is part of CutCells +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +namespace cutcells +{ + +/// Relation in a selection clause: φ < 0, φ > 0, or φ = 0. +enum class Relation +{ + LessThan, + GreaterThan, + EqualTo +}; + +/// One clause of a selection expression, e.g. "phi1 < 0". +struct Clause +{ + std::string name; ///< level-set name (e.g. "phi1") + int level_set_index = -1; ///< resolved after compile() + Relation relation; ///< the comparison operator +}; + +/// One conjunction term in a selection expression. +/// +/// A term is an AND of clauses: +/// "phi1 < 0 and phi2 = 0" -> two clauses in one term. +/// +/// After compilation against a name registry, bitmasks are populated: +/// - bit i set in zero_required → φ_i = 0 +/// - bit i set in negative_required → φ_i < 0 +/// - bit i set in positive_required → φ_i > 0 +struct SelectionTerm +{ + std::vector clauses; ///< implicitly AND-connected + + // Compiled bitmasks (set by compile_selection_expr()) + std::uint64_t zero_required = 0; + std::uint64_t negative_required = 0; + std::uint64_t positive_required = 0; +}; + +/// A compiled selection expression. +/// +/// The expression is a union (OR) of terms: +/// "phi1 < 0 or phi2 < 0" -> two terms. +/// "phi1 < 0 and phi2 > 0" -> one term with two clauses. +struct SelectionExpr +{ + std::vector terms; + + // Legacy single-term view. These fields mirror terms.front() after + // parsing/compilation so existing single-term code can be migrated + // incrementally. New selection code should use terms. + std::vector clauses; + std::uint64_t zero_required = 0; + std::uint64_t negative_required = 0; + std::uint64_t positive_required = 0; +}; + +/// Parse a selection expression string. +/// +/// Grammar: term ('or' term)* +/// term: clause ('and' clause)* +/// clause: name ('<'|'>'|'=') '0' +/// +/// @throws std::runtime_error on syntax error. +SelectionExpr parse_selection_expr(std::string_view text); + +/// Compile a parsed expression against a level-set name registry. +/// +/// Resolves each clause's name to its index in @p level_set_names +/// and populates the bitmask fields. +/// +/// @throws std::runtime_error if a name is not found. +void compile_selection_expr(SelectionExpr& expr, + const std::vector& level_set_names); + +/// Infer the entity dimension selected by a compiled expression. +/// +/// Each OR term must select the same dimension. Within a term, each EqualTo +/// clause reduces the dimension by one; a term with no EqualTo clauses selects +/// volume (tdim). +/// +/// @param tdim topological dimension of the background cells. +/// @return inferred entity dimension. +int infer_selection_dim(const SelectionExpr& expr, int tdim); + +} // namespace cutcells diff --git a/cpp/src/span_math.h b/cpp/src/span_math.h index 1bdcc60..8486c61 100644 --- a/cpp/src/span_math.h +++ b/cpp/src/span_math.h @@ -5,6 +5,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include #include #include #include @@ -15,6 +16,37 @@ namespace cutcells { namespace math { + template + using Vec = std::array; + + template + using Vec2 = std::array; + + template + using Vec3 = std::array; + + template + inline Vec to_vec(std::span a) + { + assert(a.size() == N); + Vec result{}; + for (std::size_t i = 0; i < N; ++i) + result[i] = a[i]; + return result; + } + + template + inline Vec2 to_vec2(std::span a) + { + return to_vec<2, T>(a); + } + + template + inline Vec3 to_vec3(std::span a) + { + return to_vec<3, T>(a); + } + template inline T dot(std::span a, std::span b) { @@ -28,12 +60,9 @@ namespace cutcells } template - inline std::vector cross(std::span a, std::span b) + inline Vec3 cross(const Vec3& a, const Vec3& b) { - //cross product only implemented in 3D - assert(a.size()==3); - assert(a.size()==b.size()); - std::vector result(3); + Vec3 result{}; result[0] = a[1]*b[2]-a[2]*b[1]; result[1] = a[2]*b[0]-a[0]*b[2]; @@ -43,25 +72,41 @@ namespace cutcells } template - inline std::vector subtract(std::span a, std::span b) + inline T cross(const Vec2& a, const Vec2& b) { - assert(a.size()==b.size()); - std::vector result(a.size()); - for(std::size_t i=0;i + inline Vec subtract(const Vec& a, const Vec& b) + { + Vec result{}; + for (std::size_t i = 0; i < N; ++i) + result[i] = a[i] - b[i]; return result; } template - inline std::vector add(std::span a, std::span b) + inline Vec3 subtract(const Vec3& a, const Vec3& b) { - assert(a.size()==b.size()); - std::vector result(a.size()); - for(std::size_t i=0;i(a, b); + } + + template + inline Vec add(const Vec& a, const Vec& b) + { + Vec result{}; + for (std::size_t i = 0; i < N; ++i) + result[i] = a[i] + b[i]; return result; } + template + inline Vec3 add(const Vec3& a, const Vec3& b) + { + return add<3, T>(a, b); + } + template inline T distance(std::span a, std::span b) { diff --git a/cpp/src/triangulation.h b/cpp/src/triangulation.h index 064b1d6..c4a34ad 100644 --- a/cpp/src/triangulation.h +++ b/cpp/src/triangulation.h @@ -7,26 +7,101 @@ #include "cell_types.h" +#include +#include +#include + namespace cutcells::cell { + /// @brief Decompose a non-simplex cell into simplices (triangles or tetrahedra). + /// + /// Each entry in @p tris is a list of vertex indices (into the parent vertices[]) + /// array) that form one simplex. + /// + /// Supported cell types and their outputs: + /// quadrilateral → 2 triangles (3 vertices each) + /// hexahedron → 6 tetrahedra (4 vertices each) + /// prism → 3 tetrahedra (4 vertices each) + /// pyramid → 2 tetrahedra (4 vertices each) + /// + /// Basix vertex ordering is assumed for all cell types. inline void triangulation(const type cell_type, int* vertices, std::vector>& tris) { switch(cell_type) { - case type::quadrilateral: tris.resize(2, std::vector(3)); - tris = {{vertices[0],vertices[1],vertices[2]}, {vertices[0],vertices[2],vertices[3]}}; - break; - case type::prism: // Tetrahedron 0 (original vertices): { 0, 2, 1, 3 } - // Tetrahedron 1 (original vertices): { 1, 3, 5, 4 } - // Tetrahedron 2 (original vertices): { 1, 2, 5, 3 } - tris.resize(3, std::vector(3)); - tris = {{vertices[0],vertices[2],vertices[1],vertices[3]}, - {vertices[1],vertices[3],vertices[5],vertices[4]}, - {vertices[1],vertices[2],vertices[5],vertices[3]}}; - break; - - default: throw std::invalid_argument("triangulation not implemented for given cell type"); - break; + case type::quadrilateral: + tris.resize(2, std::vector(3)); + // Basix quad: v0=(0,0), v1=(1,0), v2=(0,1), v3=(1,1) + // Split along the true diagonal v0-v3. + tris = {{vertices[0],vertices[1],vertices[3]}, + {vertices[0],vertices[3],vertices[2]}}; + break; + + case type::hexahedron: + // Basix hex: v0=(0,0,0) v1=(1,0,0) v2=(0,1,0) v3=(1,1,0) + // v4=(0,0,1) v5=(1,0,1) v6=(0,1,1) v7=(1,1,1) + // + // Kuhn triangulation (6 tets, fan through the v0→v7 diagonal). + // Each tet has |det J| = 1 on the unit cube → total volume = 6 × 1/6 = 1 ✓ + tris.resize(6, std::vector(4)); + tris = {{vertices[0],vertices[1],vertices[3],vertices[7]}, + {vertices[0],vertices[1],vertices[5],vertices[7]}, + {vertices[0],vertices[2],vertices[3],vertices[7]}, + {vertices[0],vertices[2],vertices[6],vertices[7]}, + {vertices[0],vertices[4],vertices[5],vertices[7]}, + {vertices[0],vertices[4],vertices[6],vertices[7]}}; + break; + + case type::prism: + // Basix prism matches VTK wedge: v0,v1,v2 bottom △, v3,v4,v5 top △ + // Tetrahedron 0: { v0, v2, v1, v3 } + // Tetrahedron 1: { v1, v3, v5, v4 } + // Tetrahedron 2: { v1, v2, v5, v3 } + tris.resize(3, std::vector(4)); + tris = {{vertices[0],vertices[2],vertices[1],vertices[3]}, + {vertices[1],vertices[3],vertices[5],vertices[4]}, + {vertices[1],vertices[2],vertices[5],vertices[3]}}; + break; + + case type::pyramid: + // Basix pyramid: v0=(0,0,0) v1=(1,0,0) v2=(0,1,0) v3=(1,1,0) v4=apex + // Tetrahedron 0: { v0, v1, v2, v4 } + // Tetrahedron 1: { v1, v3, v2, v4 } + tris.resize(2, std::vector(4)); + tris = {{vertices[0],vertices[1],vertices[2],vertices[4]}, + {vertices[1],vertices[3],vertices[2],vertices[4]}}; + break; + + default: + throw std::invalid_argument("triangulation not implemented for given cell type"); + break; } }; -} + + template + inline std::vector canonical_vertices(type cell_type) + { + switch (cell_type) + { + case type::interval: + return {T(0), T(1)}; + case type::triangle: + return {T(0),T(0), T(1),T(0), T(0),T(1)}; + case type::quadrilateral: + return {T(0),T(0), T(1),T(0), T(0),T(1), T(1),T(1)}; + case type::tetrahedron: + return {T(0),T(0),T(0), T(1),T(0),T(0), T(0),T(1),T(0), T(0),T(0),T(1)}; + case type::hexahedron: + return {T(0),T(0),T(0), T(1),T(0),T(0), T(0),T(1),T(0), T(1),T(1),T(0), + T(0),T(0),T(1), T(1),T(0),T(1), T(0),T(1),T(1), T(1),T(1),T(1)}; + case type::prism: + return {T(0),T(0),T(0), T(1),T(0),T(0), T(0),T(1),T(0), + T(0),T(0),T(1), T(1),T(0),T(1), T(0),T(1),T(1)}; + case type::pyramid: + return {T(0),T(0),T(0), T(1),T(0),T(0), T(0),T(1),T(0), T(1),T(1),T(0), T(0),T(0),T(1)}; + default: + throw std::invalid_argument("canonical_vertices: unsupported cell type"); + } + } + +} // namespace cutcells::cell diff --git a/cpp/src/utils.h b/cpp/src/utils.h index f5d6580..f565c16 100644 --- a/cpp/src/utils.h +++ b/cpp/src/utils.h @@ -5,6 +5,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include #include #include #include @@ -13,30 +14,24 @@ namespace cutcells::utils { + constexpr int MAX_TOKEN_LOOKUP = 256; + //Check if two vertices are equal template static bool equal(std::span coord1, const int &id1, std::span coord2, const int &id2, const int& gdim) { T tol = 1e-15; - T distance = 0; + T distance2 = 0; //Take the distance between two points with id1 and id2 and return for(std::size_t j=0;j(kv.second)] = kv.first; } + + template + void create_vertex_parent_entity_map(const std::array& token_to_vertex, + std::vector& vertex_parent_entity) + { + int max_local = -1; + for (std::size_t token = 0; token < N; ++token) + { + if (token_to_vertex[token] >= 0) + max_local = std::max(max_local, token_to_vertex[token]); + } + + vertex_parent_entity.assign(static_cast(max_local + 1), -1); + for (std::size_t token = 0; token < N; ++token) + { + const int local = token_to_vertex[token]; + if (local >= 0) + vertex_parent_entity[static_cast(local)] = static_cast(token); + } + } + + // Targeted variant: only scans the token ranges actually used by the cell type. + // Much faster than scanning all 256 entries: e.g. triangle only has 6 valid tokens. + // n_edges: number of edge tokens in [0, n_edges) + // n_vertices: number of vertex tokens in [100, 100+n_vertices) + // n_special: number of special tokens in [200, 200+n_special) + template + void create_vertex_parent_entity_map(const std::array& token_to_vertex, + std::vector& vertex_parent_entity, + int n_edges, int n_vertices, int n_special = 0) + { + int max_local = -1; + for (int t = 0; t < n_edges; ++t) + if (token_to_vertex[t] >= 0) + max_local = std::max(max_local, token_to_vertex[t]); + for (int t = 100, e = 100 + n_vertices; t < e; ++t) + if (token_to_vertex[t] >= 0) + max_local = std::max(max_local, token_to_vertex[t]); + for (int t = 200, e = 200 + n_special; t < e; ++t) + if (token_to_vertex[t] >= 0) + max_local = std::max(max_local, token_to_vertex[t]); + + vertex_parent_entity.assign(static_cast(max_local + 1), -1); + for (int t = 0; t < n_edges; ++t) + if (token_to_vertex[t] >= 0) + vertex_parent_entity[static_cast(token_to_vertex[t])] = static_cast(t); + for (int t = 100, e = 100 + n_vertices; t < e; ++t) + if (token_to_vertex[t] >= 0) + vertex_parent_entity[static_cast(token_to_vertex[t])] = static_cast(t); + for (int t = 200, e = 200 + n_special; t < e; ++t) + if (token_to_vertex[t] >= 0) + vertex_parent_entity[static_cast(token_to_vertex[t])] = static_cast(t); + } } \ No newline at end of file diff --git a/cpp/src/write_tikz.h b/cpp/src/write_tikz.h index 968e346..d70b9db 100644 --- a/cpp/src/write_tikz.h +++ b/cpp/src/write_tikz.h @@ -9,6 +9,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include "cut_cell.h" namespace cutcells::io { @@ -145,4 +153,61 @@ namespace cutcells::io } else std:: cout << "Unable to open file " << filename << " to write" << std::endl; }; + + inline void write_tikz(std::string filename, + const std::span element_vertex_coords, + const std::span connectivity, + const std::span offsets, + const std::span bg_vertex_coords, + const std::vector> bg_elements, + const std::span ls_values, + const int gdim) + { + if (offsets.empty()) + { + write_tikz(filename, element_vertex_coords, std::vector>{}, + bg_vertex_coords, bg_elements, ls_values, gdim); + return; + } + + const bool has_terminal_offset = offsets.back() == static_cast(connectivity.size()); + const int num_cells = has_terminal_offset + ? static_cast(offsets.size()) - 1 + : static_cast(offsets.size()); + + std::vector> elements; + elements.reserve(std::max(num_cells, 0)); + + for (int i = 0; i < num_cells; ++i) + { + const int begin = offsets[i]; + const int end = (i + 1 < static_cast(offsets.size())) + ? offsets[i + 1] + : static_cast(connectivity.size()); + + if (begin < 0 || end < begin || end > static_cast(connectivity.size())) + throw std::runtime_error("Invalid CSR layout in write_tikz"); + + elements.emplace_back(connectivity.begin() + begin, connectivity.begin() + end); + } + + write_tikz(filename, element_vertex_coords, elements, bg_vertex_coords, bg_elements, ls_values, gdim); + } + + inline void write_tikz(std::string filename, + const cell::CutCell& cut_cell, + const std::span bg_vertex_coords, + const std::vector> bg_elements, + const std::span ls_values, + const int gdim) + { + write_tikz(filename, + std::span(cut_cell._vertex_coords.data(), cut_cell._vertex_coords.size()), + std::span(cut_cell._connectivity.data(), cut_cell._connectivity.size()), + std::span(cut_cell._offset.data(), cut_cell._offset.size()), + bg_vertex_coords, + bg_elements, + ls_values, + gdim); + } } \ No newline at end of file diff --git a/cpp/src/write_vtk.cpp b/cpp/src/write_vtk.cpp index 4dd90a6..b4142fd 100644 --- a/cpp/src/write_vtk.cpp +++ b/cpp/src/write_vtk.cpp @@ -7,15 +7,441 @@ #include #include #include +#include +#include +#include +#include +#include #include "write_vtk.h" #include "cell_types.h" +#include "reference_cell.h" +namespace +{ + +constexpr int VTK_LAGRANGE_TRIANGLE = 69; +constexpr int VTK_LAGRANGE_TETRAHEDRON = 71; + +constexpr const char* vtk_byte_order() +{ + if constexpr (std::endian::native == std::endian::little) + return "LittleEndian"; + else + return "BigEndian"; +} + +enum class HOCellFamily +{ + triangle, + tetrahedron +}; + +int vec_pop(std::vector& v, int i) +{ + const auto pos = (i < 0) ? v.end() + i : v.begin() + i; + const int value = *pos; + v.erase(pos); + return value; +} + +int triangle_degree_from_num_nodes(const int num_nodes) +{ + const int n = static_cast((std::sqrt(1 + 8 * num_nodes) - 1) / 2); + if (2 * num_nodes != n * (n + 1)) + throw std::runtime_error("write_level_set_vtu: invalid triangle node count"); + return n - 1; +} + +int tetrahedron_degree_from_num_nodes(const int num_nodes) +{ + int n = 0; + while (n * (n + 1) * (n + 2) < 6 * num_nodes) + ++n; + if (n * (n + 1) * (n + 2) != 6 * num_nodes) + throw std::runtime_error("write_level_set_vtu: invalid tetrahedron node count"); + return n - 1; +} + +std::vector vtk_triangle_remainders(std::vector remainders) +{ + std::vector map; + map.reserve(remainders.size()); + + while (!remainders.empty()) + { + if (remainders.size() == 1) + { + map.push_back(vec_pop(remainders, 0)); + break; + } + + const int degree = triangle_degree_from_num_nodes( + static_cast(remainders.size())); + + map.push_back(vec_pop(remainders, 0)); + map.push_back(vec_pop(remainders, degree - 1)); + map.push_back(vec_pop(remainders, -1)); + + for (int i = 0; i < degree - 1; ++i) + map.push_back(vec_pop(remainders, 0)); + + for (int i = 1, k = degree * (degree - 1) / 2; i < degree; + k -= degree - i, ++i) + { + map.push_back(vec_pop(remainders, -k)); + } + + for (int i = 1, k = 1; i < degree; k += i, ++i) + map.push_back(vec_pop(remainders, -k)); + } + + return map; +} + +std::vector basix_to_vtk_triangle(const int num_nodes) +{ + std::vector map; + map.reserve(num_nodes); + map.insert(map.end(), {0, 1, 2}); + + const int degree = triangle_degree_from_num_nodes(num_nodes); + for (int k = 1; k < degree; ++k) + map.push_back(3 + 2 * (degree - 1) + k - 1); + for (int k = 1; k < degree; ++k) + map.push_back(3 + k - 1); + for (int k = 1; k < degree; ++k) + map.push_back(2 * degree - (k - 1)); + + if (degree < 3) + return map; + + std::vector rem(num_nodes - static_cast(map.size())); + std::iota(rem.begin(), rem.end(), 3 * degree); + std::vector rem_map = vtk_triangle_remainders(std::move(rem)); + map.insert(map.end(), rem_map.begin(), rem_map.end()); + return map; +} + +std::vector vtk_tetrahedron_remainders(std::vector remainders) +{ + std::vector map; + map.reserve(remainders.size()); + + while (!remainders.empty()) + { + if (remainders.size() == 1) + { + map.push_back(vec_pop(remainders, 0)); + break; + } + + const int deg = tetrahedron_degree_from_num_nodes( + static_cast(remainders.size())) + + 1; + map.push_back(vec_pop(remainders, 0)); + map.push_back(vec_pop(remainders, deg - 2)); + map.push_back(vec_pop(remainders, deg * (deg + 1) / 2 - 3)); + map.push_back(vec_pop(remainders, -1)); + + if (deg > 2) + { + for (int i = 0; i < deg - 2; ++i) + map.push_back(vec_pop(remainders, 0)); + + { + int d = deg - 2; + for (int i = 0; i < deg - 2; ++i) + { + map.push_back(vec_pop(remainders, d)); + d += (deg - 3 - i); + } + } + + { + int d = (deg - 2) * (deg - 1) / 2 - 1; + for (int i = 0; i < deg - 2; ++i) + { + map.push_back(vec_pop(remainders, d)); + d -= (2 + i); + } + } + + { + int d = (deg - 3) * (deg - 2) / 2; + for (int i = 0; i < deg - 2; ++i) + { + map.push_back(vec_pop(remainders, d)); + d += ((deg - i) * (deg - i - 1) / 2 - 1); + } + } + + { + int d = (deg - 3) * (deg - 2) / 2 + deg - 3; + for (int i = 0; i < deg - 2; ++i) + { + map.push_back(vec_pop(remainders, d)); + d += ((deg - 2 - i) * (deg - 1 - i) / 2 + deg - 4 - i); + } + } + + { + int d = (deg - 3) * (deg - 2) / 2 + deg - 3 + + (deg - 2) * (deg - 1) / 2 - 1; + for (int i = 0; i < deg - 2; ++i) + { + map.push_back(vec_pop(remainders, d)); + d += ((deg - 3 - i) * (deg - 2 - i) / 2 + deg - i - 5); + } + } + } + + if (deg > 3) + { + { + std::vector dofs; + int d = (deg - 3) * (deg - 2) / 2; + for (int i = 0; i < deg - 3; ++i) + { + for (int ii = 0; ii < deg - 3 - i; ++ii) + dofs.push_back(vec_pop(remainders, d)); + d += ((deg - 2 - i) * (deg - 1 - i) / 2 - 1); + } + + std::vector tri_map = vtk_triangle_remainders(std::move(dofs)); + map.insert(map.end(), tri_map.begin(), tri_map.end()); + } + + { + std::vector dofs; + int start = deg * deg - 4 * deg + 2; + int sub_i_start = deg - 3; + for (int i = 0; i < deg - 3; ++i) + { + int d = start; + int sub_i = sub_i_start; + for (int ii = 0; ii < deg - 3 - i; ++ii) + { + dofs.push_back(vec_pop(remainders, d)); + d += sub_i * (sub_i + 1) / 2 - 2 - i; + sub_i -= 1; + } + + start -= (2 + i); + } + + std::vector tri_map = vtk_triangle_remainders(std::move(dofs)); + map.insert(map.end(), tri_map.begin(), tri_map.end()); + } + + { + std::vector dofs; + int start = (deg - 3) * (deg - 2) / 2; + int sub_i_start = deg - 3; + for (int i = 0; i < deg - 3; ++i) + { + int d = start; + int sub_i = sub_i_start; + for (int ii = 0; ii < deg - 3 - i; ++ii) + { + dofs.push_back(vec_pop(remainders, d)); + d += sub_i * (sub_i + 1) / 2 - 1 - 2 * i; + sub_i -= 1; + } + + start += (deg - 4 - i); + } + + std::vector tri_map = vtk_triangle_remainders(std::move(dofs)); + map.insert(map.end(), tri_map.begin(), tri_map.end()); + } + + { + std::vector dofs; + int add_start = deg - 4; + for (int i = 0; i < deg - 3; ++i) + { + int d = 0; + int add = add_start; + for (int ii = 0; ii < deg - 3 - i; ++ii) + { + dofs.push_back(vec_pop(remainders, d)); + d += add; + add -= 1; + } + + add_start -= 1; + } + + std::vector tri_map = vtk_triangle_remainders(std::move(dofs)); + map.insert(map.end(), tri_map.begin(), tri_map.end()); + } + } + } + + return map; +} + +std::vector basix_to_vtk_tetrahedron(const int num_nodes) +{ + const int degree = tetrahedron_degree_from_num_nodes(num_nodes); + std::vector map; + map.reserve(num_nodes); + map.insert(map.end(), {0, 1, 2, 3}); + + if (degree < 2) + return map; + + int base = 4; + const int edge_dofs = degree - 1; + for (int edge : {5, 2, 4, 3, 1, 0}) + { + if (edge == 4) + { + for (int i = 0; i < edge_dofs; ++i) + map.push_back(base + edge_dofs * (edge + 1) - 1 - i); + } + else + { + for (int i = 0; i < edge_dofs; ++i) + map.push_back(base + edge_dofs * edge + i); + } + } + + if (degree < 3) + return map; + + base += 6 * edge_dofs; + const int n_face_dofs = (degree - 1) * (degree - 2) / 2; + for (int face : {2, 0, 1, 3}) + { + std::vector face_dofs; + face_dofs.reserve(n_face_dofs); + if (face == 2) + { + for (int i = 0; i < n_face_dofs; ++i) + face_dofs.push_back(base + n_face_dofs * face + i); + } + else if (face == 0) + { + for (int i = degree - 3; i >= 0; --i) + { + int d = i; + for (int ii = 0; ii <= i; ++ii) + { + face_dofs.push_back(base + n_face_dofs * face + d); + d += degree - 3 - ii; + } + } + } + else + { + for (int i = 0; i < degree - 2; ++i) + { + int d = i; + for (int ii = 0; ii < degree - 2 - i; ++ii) + { + face_dofs.push_back(base + n_face_dofs * face + d); + d += degree - 2 - ii; + } + } + } + + std::vector face_map = vtk_triangle_remainders(std::move(face_dofs)); + map.insert(map.end(), face_map.begin(), face_map.end()); + } + + if (degree < 4) + return map; + + base += 4 * n_face_dofs; + std::vector remainders((degree - 1) * (degree - 2) * (degree - 3) / 6); + std::iota(remainders.begin(), remainders.end(), base); + std::vector rem_map = vtk_tetrahedron_remainders(std::move(remainders)); + map.insert(map.end(), rem_map.begin(), rem_map.end()); + return map; +} + +HOCellFamily infer_cell_family(const cutcells::LevelSetMeshData& mesh_data, + const int cell_id) +{ + if (!mesh_data.cell_types.empty()) + { + const cutcells::cell::type ct = mesh_data.cell_types[static_cast(cell_id)]; + if (ct == cutcells::cell::type::triangle) + { + return HOCellFamily::triangle; + } + if (ct == cutcells::cell::type::tetrahedron) + { + return HOCellFamily::tetrahedron; + } + + throw std::runtime_error( + "write_level_set_vtu: unsupported cell type " + + cutcells::cell::cell_type_to_str(ct)); + } + + if (mesh_data.tdim == 2) + return HOCellFamily::triangle; + if (mesh_data.tdim == 3) + return HOCellFamily::tetrahedron; + + throw std::runtime_error( + "write_level_set_vtu: unsupported tdim without cell_types (supported tdim: 2, 3)"); +} + +int expected_local_dofs(const HOCellFamily family, const int degree) +{ + if (family == HOCellFamily::triangle) + return (degree + 1) * (degree + 2) / 2; + return (degree + 1) * (degree + 2) * (degree + 3) / 6; +} + +std::vector basix_to_vtk_lagrange_permutation_impl(const HOCellFamily family, + const int local_dofs, + const int degree) +{ + if (degree < 2 || degree > 4) + { + throw std::runtime_error( + "basix_to_vtk_lagrange_permutation: unsupported polynomial degree " + std::to_string(degree) + + " (supported: 2, 3, 4)"); + } + + if (local_dofs != expected_local_dofs(family, degree)) + { + throw std::runtime_error( + "basix_to_vtk_lagrange_permutation: local dof count does not match cell family/degree"); + } + + if (family == HOCellFamily::triangle) + return basix_to_vtk_triangle(local_dofs); + return basix_to_vtk_tetrahedron(local_dofs); +} + +} // namespace namespace cutcells::io { + std::vector basix_to_vtk_lagrange_permutation(cell::type cell_type, + int local_dofs, + int degree) + { + HOCellFamily family; + if (cell_type == cell::type::triangle) + family = HOCellFamily::triangle; + else if (cell_type == cell::type::tetrahedron) + family = HOCellFamily::tetrahedron; + else + throw std::runtime_error( + "basix_to_vtk_lagrange_permutation: supported cells are triangle and tetrahedron"); + return basix_to_vtk_lagrange_permutation_impl(family, local_dofs, degree); + } + void write_vtk(std::string filename, const std::span element_vertex_coords, - const std::vector> elements, + const std::span connectivity, + const std::span offsets, const std::span element_types, const int gdim) { @@ -25,10 +451,11 @@ namespace cutcells::io if(ofs) { int num_points = element_vertex_coords.size()/gdim; - int num_cells = elements.size(); + int num_cells = static_cast(offsets.size()) - 1; ofs << "\n" - << "\n" + << "\n" << "\t\n" << "\t\t\n"; @@ -55,24 +482,14 @@ namespace cutcells::io ofs << "\t\t\t\n"; ofs << "\t\t\t\n"; ofs << "\t\t\t "; - for(auto& element: elements) - { - for(auto& vertex_id: element) - { - ofs << vertex_id << " "; - - } - } + for(auto& vertex_id: connectivity) + ofs << vertex_id << " "; ofs << "\n"; ofs << "\t\t\t "; - int offset = 0; - for(auto& element: elements) - { - offset +=element.size(); - ofs << offset << " "; - } + for(std::size_t i=1;i\n"; - ofs << "\t\t\t "; + ofs << "\t\t\t "; for(auto& type: element_types) { ofs << static_cast(cell::map_cell_type_to_vtk(type)) << " "; @@ -92,9 +509,205 @@ namespace cutcells::io void write_vtk(std::string filename, cell::CutCell& cut_cell) { //std::as_const() - write_vtk(filename, cut_cell._vertex_coords, cut_cell._connectivity, + write_vtk(filename, cut_cell._vertex_coords, cut_cell._connectivity, cut_cell._offset, cut_cell._types, cut_cell._gdim); } + void write_level_set_vtu(std::string filename, + const cutcells::LevelSetFunction& ls, + std::string field_name) + { + if (!ls.has_mesh_data()) + throw std::runtime_error("write_level_set_vtu: level set has no mesh_data"); + if (!ls.has_dof_values()) + throw std::runtime_error("write_level_set_vtu: level set has no dof_values"); + + const auto& mesh_data = *ls.mesh_data; + if (mesh_data.gdim < 2 || mesh_data.gdim > 3) + { + throw std::runtime_error( + "write_level_set_vtu: unsupported geometric dimension " + + std::to_string(mesh_data.gdim) + " (supported: 2 or 3)"); + } + + if (ls.dof_values.size() != static_cast(mesh_data.num_dofs())) + { + throw std::runtime_error( + "write_level_set_vtu: dof_values size does not match mesh_data.num_dofs()"); + } + + const int num_cells = mesh_data.num_cells(); + std::vector connectivity; + connectivity.reserve(mesh_data.cell_dofs.size()); + std::vector offsets; + offsets.reserve(static_cast(num_cells)); + std::vector types; + types.reserve(static_cast(num_cells)); + + for (int cell_id = 0; cell_id < num_cells; ++cell_id) + { + const std::span cell_dofs = mesh_data.cell_dofs_span(cell_id); + const HOCellFamily family = infer_cell_family(mesh_data, cell_id); + const std::vector perm = basix_to_vtk_lagrange_permutation_impl( + family, + static_cast(cell_dofs.size()), + mesh_data.degree); + + for (const int p : perm) + connectivity.push_back(cell_dofs[static_cast(p)]); + + offsets.push_back(static_cast(connectivity.size())); + types.push_back( + (family == HOCellFamily::triangle) + ? VTK_LAGRANGE_TRIANGLE + : VTK_LAGRANGE_TETRAHEDRON); + } + + std::ofstream ofs(filename.c_str(), std::ios::out); + if (!ofs) + throw std::runtime_error("write_level_set_vtu: unable to open file " + filename); + + const int num_points = mesh_data.num_dofs(); + ofs << "\n" + << "\n" + << "\t\n" + << "\t\t\n"; + + ofs << "\t\t\t\n" + << "\t\t\t "; + for (int i = 0; i < num_points; ++i) + { + const std::size_t base = static_cast(i) * static_cast(mesh_data.gdim); + const double x = mesh_data.dof_coordinates[base]; + const double y = mesh_data.dof_coordinates[base + 1]; + const double z = (mesh_data.gdim == 3) ? mesh_data.dof_coordinates[base + 2] : 0.0; + ofs << x << " " << y << " " << z << " "; + } + ofs << "\n" + << "\t\t\t\n"; + + ofs << "\t\t\t\n" + << "\t\t\t "; + for (const double value : ls.dof_values) + ofs << value << " "; + ofs << "\n" + << "\t\t\t\n"; + + ofs << "\t\t\t\n"; + ofs << "\t\t\t "; + for (const int dof : connectivity) + ofs << dof << " "; + ofs << "\n"; + + ofs << "\t\t\t "; + for (const int off : offsets) + ofs << off << " "; + ofs << "\n"; + + ofs << "\t\t\t "; + for (const int type : types) + ofs << type << " "; + ofs << "\n"; + ofs << "\t\t\t\n"; + + ofs << "\t\t\n" + << "\t\n" + << "\n"; + } + + void write_lagrange_vtk(std::string filename, + const std::span point_coords, + const std::span connectivity, + const std::span offsets, + const std::span vtk_types, + int gdim, + const std::span parent_map, + const std::span subdivision_depth) + { + if (gdim < 1 || gdim > 3) + throw std::runtime_error("write_lagrange_vtk: gdim must be 1, 2, or 3"); + if (offsets.empty()) + throw std::runtime_error("write_lagrange_vtk: offsets must not be empty"); + + const int num_points = static_cast(point_coords.size()) / gdim; + const int num_cells = static_cast(offsets.size()) - 1; + + auto check_cell_data = [num_cells](std::span data, + const char* name) + { + if (!data.empty() && static_cast(data.size()) != num_cells) + throw std::runtime_error(std::string("write_lagrange_vtk: cell data size mismatch for ") + name); + }; + check_cell_data(parent_map, "parent_map"); + check_cell_data(subdivision_depth, "subdivision_depth"); + + std::ofstream ofs(filename.c_str(), std::ios::out); + if (!ofs) + throw std::runtime_error("write_lagrange_vtk: unable to open file " + filename); + + ofs << "\n" + << "\n" + << "\t\n" + << "\t\t\n"; + + ofs << "\t\t\t\n" + << "\t\t\t "; + for (int i = 0; i < num_points; ++i) + { + const std::size_t base = static_cast(i) * static_cast(gdim); + const double x = point_coords[base]; + const double y = (gdim >= 2) ? point_coords[base + 1] : 0.0; + const double z = (gdim >= 3) ? point_coords[base + 2] : 0.0; + ofs << x << " " << y << " " << z << " "; + } + ofs << "\n" + << "\t\t\t\n"; + + if (!parent_map.empty() || !subdivision_depth.empty()) + { + ofs << "\t\t\t\n"; + auto write_i32_data = [&ofs](std::span data, + const char* name) + { + if (data.empty()) + return; + ofs << "\t\t\t "; + for (const auto v : data) + ofs << v << " "; + ofs << "\n"; + }; + write_i32_data(parent_map, "parent_id"); + write_i32_data(subdivision_depth, "subdivision_depth"); + ofs << "\t\t\t\n"; + } + + ofs << "\t\t\t\n"; + ofs << "\t\t\t "; + for (const int dof : connectivity) + ofs << dof << " "; + ofs << "\n"; + + ofs << "\t\t\t "; + for (int i = 1; i < static_cast(offsets.size()); ++i) + ofs << offsets[static_cast(i)] << " "; + ofs << "\n"; + + ofs << "\t\t\t "; + for (const int type : vtk_types) + ofs << type << " "; + ofs << "\n"; + ofs << "\t\t\t\n"; + + ofs << "\t\t\n" + << "\t\n" + << "\n"; + } + } diff --git a/cpp/src/write_vtk.h b/cpp/src/write_vtk.h index 1ee5b0c..be68f53 100644 --- a/cpp/src/write_vtk.h +++ b/cpp/src/write_vtk.h @@ -6,14 +6,112 @@ #pragma once #include +#include +#include +#include #include "cut_cell.h" +#include "cut_mesh.h" +#include "reference_cell.h" +#include "level_set.h" namespace cutcells::io { + std::vector basix_to_vtk_lagrange_permutation(cell::type cell_type, + int local_dofs, + int degree); + void write_vtk(std::string filename, const std::span element_vertex_coords, - const std::vector> elements, + const std::span connectivity, + const std::span offsets, const std::span element_types, const int gdim); void write_vtk(std::string filename, cell::CutCell& cut_cell); -} \ No newline at end of file + + /// Write a CutMesh (basix-ordered internally) to a VTU file. + /// Non-simplex cells are permuted to VTK vertex ordering before writing. + template + void write_vtk(std::string filename, const mesh::CutMesh& cut_mesh) + { + const int n_cells = cut_mesh._num_cells; + const int gdim = cut_mesh._gdim; + + // Permute non-simplex cell connectivity from basix to VTK ordering. + std::vector vtk_connectivity; + vtk_connectivity.reserve(cut_mesh._connectivity.size()); + + for (int c = 0; c < n_cells; ++c) + { + const int start = (c == 0) ? 0 : cut_mesh._offset[static_cast(c)]; + const int end = cut_mesh._offset[static_cast(c + 1)]; + const int nv = end - start; + const cell::type ctype = cut_mesh._types[static_cast(c)]; + + if (ctype == cell::type::point + || ctype == cell::type::interval + || ctype == cell::type::triangle + || ctype == cell::type::tetrahedron) + { + for (int k = start; k < end; ++k) + vtk_connectivity.push_back(cut_mesh._connectivity[static_cast(k)]); + } + else + { + const auto perm = cell::basix_to_vtk_vertex_permutation(ctype); + if (static_cast(perm.size()) != nv) + throw std::runtime_error("write_vtk(CutMesh): cell vertex count mismatch"); + for (int j = 0; j < nv; ++j) + vtk_connectivity.push_back( + cut_mesh._connectivity[static_cast( + start + perm[static_cast(j)])]); + } + } + + // write_vtk low-level function accepts only double coords; convert if needed. + std::vector coords_d(cut_mesh._vertex_coords.begin(), + cut_mesh._vertex_coords.end()); + write_vtk( + filename, + std::span(coords_d.data(), coords_d.size()), + std::span(vtk_connectivity.data(), vtk_connectivity.size()), + std::span(cut_mesh._offset.data(), cut_mesh._offset.size()), + std::span(const_cast(cut_mesh._types.data()), + cut_mesh._types.size()), + gdim); + } + + void write_level_set_vtu(std::string filename, + const cutcells::LevelSetFunction& ls, + std::string field_name = "phi"); + + void write_lagrange_vtk(std::string filename, + const std::span point_coords, + const std::span connectivity, + const std::span offsets, + const std::span vtk_types, + int gdim, + const std::span parent_map = {}, + const std::span subdivision_depth = {}); + + template + void write_lagrange_vtk(std::string filename, + const std::span point_coords, + const std::span connectivity, + const std::span offsets, + const std::span vtk_types, + int gdim, + const std::span parent_map = {}, + const std::span subdivision_depth = {}) + { + std::vector coords_d(point_coords.begin(), point_coords.end()); + write_lagrange_vtk( + filename, + std::span(coords_d.data(), coords_d.size()), + connectivity, + offsets, + vtk_types, + gdim, + parent_map, + subdivision_depth); + } +} diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 2c7b995..6de86a8 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,84 +1,187 @@ cmake_minimum_required(VERSION 3.19) +project(cutcells_nanobind VERSION "0.2.0" LANGUAGES CXX) + # nanobind uses aligned deallocators only present on macOS > 10.14 -if(APPLE) +if(APPLE AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") endif() -project(cutcells_nanobind VERSION "0.2.0" LANGUAGES CXX) - -# Set C++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -if (TARGET cutcells) - add_library(CUTCELLS::cutcells ALIAS cutcells) -else() - # Find CutCells (C++) - # Prefer the in-repo build if present, so `pip install -e python` uses local C++ changes. - if (NOT DEFINED CutCells_DIR) - set(_local_cutcells_prefix "${CMAKE_CURRENT_LIST_DIR}/../cpp/build-dir/prefix") - if (EXISTS "${_local_cutcells_prefix}/lib/cmake/cutcells/CutCellsConfig.cmake") - list(PREPEND CMAKE_PREFIX_PATH "${_local_cutcells_prefix}") +set(_conda_prefix "$ENV{CONDA_PREFIX}") +if(_conda_prefix) + # Prefer the active conda environment over the base installation. + if(NOT DEFINED Python_ROOT_DIR) + set(Python_ROOT_DIR "${_conda_prefix}") + endif() + if(NOT DEFINED Python_FIND_VIRTUALENV) + set(Python_FIND_VIRTUALENV ONLY) + endif() + if(NOT DEFINED Python_FIND_STRATEGY) + set(Python_FIND_STRATEGY LOCATION) + endif() + if(NOT DEFINED Python_EXECUTABLE) + if(WIN32) + set(_conda_python "${_conda_prefix}/python.exe") + else() + set(_conda_python "${_conda_prefix}/bin/python") + endif() + if(EXISTS "${_conda_python}") + set(Python_EXECUTABLE "${_conda_python}") endif() endif() - find_package(CutCells REQUIRED) + + list(PREPEND CMAKE_PREFIX_PATH + "${_conda_prefix}" + "${_conda_prefix}/lib/cmake" + "${_conda_prefix}/Library" + "${_conda_prefix}/Library/lib/cmake") endif() -if(CUTCELLS_FOUND) - message(STATUS "Found CutCells at ${CUTCELLS_DIR}") +# Imported CutCells targets may depend on OpenMP::OpenMP_CXX. +# Keep this optional and quiet so builds without OpenMP still proceed. +find_package(OpenMP QUIET COMPONENTS CXX) + +option(CUTCELLS_PYTHON_USE_LOCAL_CPP + "Build the bundled CutCells C++ library from source with the same compiler as the Python extension." + ON) + +function(_set_default_cutcells_dir) + if(DEFINED CutCells_DIR) + return() + endif() + + set(_candidates + "${CMAKE_CURRENT_LIST_DIR}/../cpp/build/prefix/lib/cmake/cutcells" + "${CMAKE_CURRENT_LIST_DIR}/../cpp/build-noomp/prefix/lib/cmake/cutcells" + "${CMAKE_CURRENT_LIST_DIR}/../cpp/build-bench-release/prefix/lib/cmake/cutcells" + "${CMAKE_CURRENT_LIST_DIR}/../cpp/build-dir/prefix/lib/cmake/cutcells") + + set(_best_dir "") + set(_best_ts -1) + foreach(_dir IN LISTS _candidates) + if(EXISTS "${_dir}/CutCellsConfig.cmake") + set(_lib "${_dir}/../../libcutcells.dylib") + if(EXISTS "${_lib}") + file(TIMESTAMP "${_lib}" _ts "%s" UTC) + else() + file(TIMESTAMP "${_dir}/CutCellsConfig.cmake" _ts "%s" UTC) + endif() + if(_ts GREATER _best_ts) + set(_best_ts "${_ts}") + set(_best_dir "${_dir}") + endif() + endif() + endforeach() + if(_best_dir) + set(CutCells_DIR "${_best_dir}" PARENT_SCOPE) + endif() +endfunction() + +if(CUTCELLS_PYTHON_USE_LOCAL_CPP + AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/../cpp/CMakeLists.txt" + AND NOT DEFINED CutCells_DIR + AND NOT TARGET CUTCELLS::cutcells + AND NOT TARGET cutcells) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../cpp" + "${CMAKE_CURRENT_BINARY_DIR}/cutcells_cpp") +endif() + +if(TARGET cutcells) + add_library(CUTCELLS::cutcells ALIAS cutcells) + if(APPLE) + set_target_properties(cutcells PROPERTIES + INSTALL_NAME_DIR "@loader_path/../lib") + endif() +elseif(NOT TARGET CUTCELLS::cutcells) + _set_default_cutcells_dir() + find_package(CutCells REQUIRED CONFIG) +endif() + +if(CutCells_FOUND) + message(STATUS "Found CutCells in: ${CutCells_DIR}") +endif() + +set(_cutcells_lib "") +if(TARGET cutcells) + set(_cutcells_lib "$") +elseif(TARGET CUTCELLS::cutcells) + get_target_property(_cutcells_lib_rel CUTCELLS::cutcells IMPORTED_LOCATION_RELEASE) + get_target_property(_cutcells_lib_dbg CUTCELLS::cutcells IMPORTED_LOCATION_DEBUG) + get_target_property(_cutcells_lib_any CUTCELLS::cutcells IMPORTED_LOCATION) + if(_cutcells_lib_rel) + set(_cutcells_lib "${_cutcells_lib_rel}") + elseif(_cutcells_lib_dbg) + set(_cutcells_lib "${_cutcells_lib_dbg}") + elseif(_cutcells_lib_any) + set(_cutcells_lib "${_cutcells_lib_any}") + endif() +endif() + +if(_cutcells_lib AND NOT TARGET cutcells) + message(STATUS "Using CutCells library: ${_cutcells_lib}") + find_program(NM_EXECUTABLE nm) + if(NM_EXECUTABLE) + execute_process( + COMMAND "${NM_EXECUTABLE}" -gU "${_cutcells_lib}" + OUTPUT_VARIABLE _nm_out + ERROR_QUIET + ) + string(FIND "${_nm_out}" "create_level_set_function" _has_create_level_set_function) + string(FIND "${_nm_out}" "write_level_set_vtu" _has_write_level_set_vtu) + if(_has_create_level_set_function EQUAL -1 OR _has_write_level_set_vtu EQUAL -1) + message(FATAL_ERROR + "The selected libcutcells is stale and missing required symbols " + "(create_level_set_function / write_level_set_vtu).\n" + "Selected library: ${_cutcells_lib}\n" + "Rebuild and reinstall the C++ core, then rerun the Python build.") + endif() + endif() endif() find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +message(STATUS "Using Python executable: ${Python_EXECUTABLE}") +if(_conda_prefix) + message(STATUS "Using active conda prefix: ${_conda_prefix}") +endif() # Detect the installed nanobind package and import it into CMake execute_process( COMMAND "${Python_EXECUTABLE}" -c "import nanobind; print(nanobind.cmake_dir())" - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) + OUTPUT_VARIABLE NB_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") find_package(nanobind CONFIG REQUIRED) -# Create the binding library nanobind_add_module(_cutcellscpp cutcells/wrapper.cpp) target_link_libraries(_cutcellscpp PRIVATE CUTCELLS::cutcells) -# Add strict compiler flags include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-Wall -Werror -Wextra -Wno-comment -pedantic" HAVE_PEDANTIC) - +check_cxx_compiler_flag("-Wall -Wextra -Wno-comment -pedantic" HAVE_PEDANTIC) if(HAVE_PEDANTIC) - target_compile_options(_cutcellscpp PRIVATE -Wall;-Wextra;-Werror;-Wno-comment) + target_compile_options(_cutcellscpp PRIVATE -Wall -Wextra -Wno-comment) endif() -get_target_property(_location CUTCELLS::cutcells LOCATION) -get_filename_component(_cutcells_dir ${_location} DIRECTORY) - -# Ensure we load the CutCells library we just found/linked against. -# In conda environments it's common to also have a (possibly older) libcutcells.dylib -# available on the default RPATH; if that appears before our in-repo prefix, dyld -# will pick the wrong one at runtime. -set_target_properties(_cutcellscpp PROPERTIES - INSTALL_RPATH "${_cutcells_dir}" - BUILD_RPATH "${_cutcells_dir}" - BUILD_WITH_INSTALL_RPATH TRUE - INSTALL_RPATH_USE_LINK_PATH FALSE) - -if(APPLE AND DEFINED ENV{CONDA_PREFIX}) - # scikit-build/nanobind often injects ${CONDA_PREFIX}/lib as an LC_RPATH ahead of ours. - # If that environment contains another libcutcells.dylib, dyld will load the wrong one. - # Reorder RPATHs post-link so the in-repo CutCells lib directory is searched first. - set(_conda_lib "$ENV{CONDA_PREFIX}/lib") - # If CutCells itself is installed into the conda env lib dir, then _cutcells_dir == _conda_lib - # and reordering is unnecessary (and would attempt to add the same RPATH twice). - if (NOT "${_cutcells_dir}" STREQUAL "${_conda_lib}") - add_custom_command(TARGET _cutcellscpp POST_BUILD - COMMAND install_name_tool -delete_rpath "${_conda_lib}" $ || true - COMMAND install_name_tool -delete_rpath "${_cutcells_dir}" $ || true - COMMAND install_name_tool -add_rpath "${_cutcells_dir}" $ - COMMAND install_name_tool -add_rpath "${_conda_lib}" $ - VERBATIM) +set(_build_rpath "$") +if(TARGET cutcells) + if(APPLE) + set(_install_rpath "@loader_path/../lib") + else() + set(_install_rpath "$ORIGIN/../lib") endif() +else() + set(_install_rpath "$") endif() +set_target_properties(_cutcellscpp PROPERTIES + BUILD_RPATH "${_build_rpath}" + INSTALL_RPATH "${_install_rpath}" + BUILD_WITH_INSTALL_RPATH FALSE + INSTALL_RPATH_USE_LINK_PATH FALSE +) + install(TARGETS _cutcellscpp LIBRARY DESTINATION cutcells) diff --git a/python/cutcells/__init__.py b/python/cutcells/__init__.py index 1902fdb..02215cb 100644 --- a/python/cutcells/__init__.py +++ b/python/cutcells/__init__.py @@ -3,7 +3,270 @@ # This file is part of CutCells # # SPDX-License-Identifier: MIT -from ._cutcellscpp import (classify_cell_domain, CellType, CutCell_float32, CutCell_float64, CutCells_float32, - CutCells_float64, CutMesh_float32, CutMesh_float64, cut, higher_order_cut, create_cut_mesh, - locate_cells, cut_vtk_mesh) +import importlib as _importlib +from importlib import util as _importlib_util +from pathlib import Path as _Path +import ctypes as _ctypes +import sys as _sys +import sysconfig as _sysconfig + + +def _load_cpp_module(): + build_dir = _Path(__file__).resolve().parents[1] / "build" + candidates = sorted(build_dir.glob("_cutcellscpp*.so")) + if candidates: + ext_suffix = _sysconfig.get_config_var("EXT_SUFFIX") + if ext_suffix: + matching = [candidate for candidate in candidates if candidate.name.endswith(ext_suffix)] + if matching: + candidates = matching + + # If we're running from a source tree, prefer an up-to-date in-tree build. + # The repo keeps `cutcells/wrapper.cpp` next to this file; if the extension + # is older than the wrapper source or the C++ library sources, it's stale. + wrapper_cpp = _Path(__file__).with_name("wrapper.cpp") + source_roots = [wrapper_cpp] + cpp_src = _Path(__file__).resolve().parents[2] / "cpp" / "src" + if cpp_src.exists(): + try: + source_roots.extend(cpp_src.glob("*.cpp")) + source_roots.extend(cpp_src.glob("*.h")) + except OSError: + pass + source_roots = [p for p in source_roots if p.exists()] + if source_roots: + try: + newest_source_ts = max(p.stat().st_mtime for p in source_roots) + candidates = [ + c for c in candidates + if c.exists() and c.stat().st_mtime >= newest_source_ts + ] + except OSError: + pass + + # Choose the newest candidate (by mtime) to avoid picking an old leftover. + if candidates: + try: + candidates = sorted(candidates, key=lambda p: p.stat().st_mtime, reverse=True) + except OSError: + pass + + lib_candidates = [ + build_dir / "cutcells_cpp" / "src" / "libcutcells.dylib", + _Path(__file__).resolve().parents[2] + / "cpp" + / "build" + / "src" + / "libcutcells.dylib", + ] + for lib in lib_candidates: + if lib.exists(): + _ctypes.CDLL(str(lib), mode=_ctypes.RTLD_GLOBAL) + break + + if candidates: + try: + spec = _importlib_util.spec_from_file_location( + "cutcells._cutcellscpp", candidates[0] + ) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load extension from {candidates[0]}") + + module = _importlib_util.module_from_spec(spec) + _sys.modules["cutcells._cutcellscpp"] = module + spec.loader.exec_module(module) + + # Basic API sanity check for common dev/test usage. If this fails, + # fall back to the installed extension module. + if not ( + hasattr(module, "HOMeshPart_float64") + and hasattr(module.HOMeshPart_float64, "visualization_mesh") + ): + raise ImportError(f"Stale in-tree extension loaded from {candidates[0]}") + + return module + except Exception: + _sys.modules.pop("cutcells._cutcellscpp", None) + + # If the extension was installed into the package directory (normal wheel + # install), prefer loading it from right next to this __init__.py. + # The sys.path scan below intentionally skips this package dir to avoid + # re-import loops in a source-tree checkout, so without this branch an + # installed package cannot load its own extension. + this_pkg_dir = _Path(__file__).resolve().parent + ext_suffix = _sysconfig.get_config_var("EXT_SUFFIX") + local_sos = [] + try: + for so in this_pkg_dir.glob("_cutcellscpp*.so"): + if ext_suffix and not so.name.endswith(ext_suffix): + continue + local_sos.append(so) + except Exception: + local_sos = [] + + if local_sos: + local_sos = sorted(local_sos, key=lambda p: p.stat().st_mtime, reverse=True) + so_path = local_sos[0] + + lib_candidates = [ + this_pkg_dir / "libcutcells.dylib", + this_pkg_dir.parent / "libcutcells.dylib", + this_pkg_dir.parent / "lib" / "libcutcells.dylib", + ] + for lib in lib_candidates: + try: + if lib.exists(): + _ctypes.CDLL(str(lib), mode=_ctypes.RTLD_GLOBAL) + break + except Exception: + pass + + return _importlib.import_module("cutcells._cutcellscpp") + + # Fall back to an installed extension (e.g. site-packages) when running from + # a source tree without an up-to-date in-tree build. + installed = [] + for entry in list(_sys.path): + try: + pkg_dir = _Path(entry) / "cutcells" + if not pkg_dir.exists() or pkg_dir.resolve() == this_pkg_dir: + continue + for so in pkg_dir.glob("_cutcellscpp*.so"): + if ext_suffix and not so.name.endswith(ext_suffix): + continue + installed.append(so) + except Exception: + continue + + if installed: + installed = sorted(installed, key=lambda p: p.stat().st_mtime, reverse=True) + so_path = installed[0] + + # Try to preload the companion dylib if it was packaged separately. + lib_candidates = [ + so_path.parent / "libcutcells.dylib", + so_path.parent.parent / "libcutcells.dylib", + so_path.parent.parent / "lib" / "libcutcells.dylib", + ] + for lib in lib_candidates: + try: + if lib.exists(): + _ctypes.CDLL(str(lib), mode=_ctypes.RTLD_GLOBAL) + break + except Exception: + pass + + spec = _importlib_util.spec_from_file_location("cutcells._cutcellscpp", so_path) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load installed extension from {so_path}") + module = _importlib_util.module_from_spec(spec) + _sys.modules["cutcells._cutcellscpp"] = module + spec.loader.exec_module(module) + return module + + raise ImportError( + "Could not load cutcells extension: no in-tree build found and no installed " + "cutcells/_cutcellscpp*.so found on sys.path." + ) + + +_cutcellscpp = _load_cpp_module() + +classify_cell_domain = _cutcellscpp.classify_cell_domain +CellType = _cutcellscpp.CellType +CutCell_float32 = _cutcellscpp.CutCell_float32 +CutCell_float64 = _cutcellscpp.CutCell_float64 +CutCells_float32 = _cutcellscpp.CutCells_float32 +CutCells_float64 = _cutcellscpp.CutCells_float64 +CutMesh_float32 = _cutcellscpp.CutMesh_float32 +CutMesh_float64 = _cutcellscpp.CutMesh_float64 +QuadratureRules_float32 = _cutcellscpp.QuadratureRules_float32 +QuadratureRules_float64 = _cutcellscpp.QuadratureRules_float64 +EdgeRootTag = _cutcellscpp.EdgeRootTag +CellCertTag = _cutcellscpp.CellCertTag +FaceCertTag = _cutcellscpp.FaceCertTag +MeshView = _cutcellscpp.MeshView +MeshView_float32 = _cutcellscpp.MeshView_float32 +MeshView_float64 = _cutcellscpp.MeshView_float64 +AdaptCell = _cutcellscpp.AdaptCell +AdaptCell_float32 = _cutcellscpp.AdaptCell_float32 +AdaptCell_float64 = _cutcellscpp.AdaptCell_float64 +LevelSetCell = _cutcellscpp.LevelSetCell +LevelSetCell_float32 = _cutcellscpp.LevelSetCell_float32 +LevelSetCell_float64 = _cutcellscpp.LevelSetCell_float64 +LevelSetMeshData = _cutcellscpp.LevelSetMeshData +LevelSetMeshData_float32 = _cutcellscpp.LevelSetMeshData_float32 +LevelSetMeshData_float64 = _cutcellscpp.LevelSetMeshData_float64 +LevelSetFunction = _cutcellscpp.LevelSetFunction +LevelSetFunction_float32 = _cutcellscpp.LevelSetFunction_float32 +LevelSetFunction_float64 = _cutcellscpp.LevelSetFunction_float64 +create_level_set_mesh_data = _cutcellscpp.create_level_set_mesh_data +create_level_set_function = _cutcellscpp.create_level_set_function +create_level_set = _cutcellscpp.create_level_set +interpolate_level_set = _cutcellscpp.interpolate_level_set +make_adapt_cell = _cutcellscpp.make_adapt_cell +build_edges = _cutcellscpp.build_edges +make_cell_level_set = _cutcellscpp.make_cell_level_set +evaluate_bernstein = _cutcellscpp.evaluate_bernstein +extract_parent_edge_bernstein = _cutcellscpp.extract_parent_edge_bernstein +restrict_edge_bernstein_exact = _cutcellscpp.restrict_edge_bernstein_exact +classify_edge_roots = _cutcellscpp.classify_edge_roots +classify_new_edges = _cutcellscpp.classify_new_edges +fill_all_vertex_signs_from_level_set = _cutcellscpp.fill_all_vertex_signs_from_level_set +classify_leaf_cells = _cutcellscpp.classify_leaf_cells +process_ready_to_cut_cells = _cutcellscpp.process_ready_to_cut_cells +refine_ready_cell_on_largest_midpoint_value = ( + _cutcellscpp.refine_ready_cell_on_largest_midpoint_value +) +refine_green_on_multiple_root_edges = _cutcellscpp.refine_green_on_multiple_root_edges +refine_red_on_ambiguous_cells = _cutcellscpp.refine_red_on_ambiguous_cells +certify_and_refine = _cutcellscpp.certify_and_refine +certify_refine_and_process_ready_cells = ( + _cutcellscpp.certify_refine_and_process_ready_cells +) +cut = _cutcellscpp.cut +higher_order_cut = _cutcellscpp.higher_order_cut +create_cut_mesh = _cutcellscpp.create_cut_mesh +locate_cells = _cutcellscpp.locate_cells +cut_vtk_mesh = _cutcellscpp.cut_vtk_mesh +cut_mesh_view = _cutcellscpp.cut_mesh_view +csr_to_vtk_cells = _cutcellscpp.csr_to_vtk_cells +compute_physical_cut_vertices = _cutcellscpp.compute_physical_cut_vertices +complete_from_physical = _cutcellscpp.complete_from_physical +make_quadrature = _cutcellscpp.make_quadrature +runtime_quadrature = _cutcellscpp.runtime_quadrature +physical_points = _cutcellscpp.physical_points +write_vtk = _cutcellscpp.write_vtk +write_level_set_vtu = _cutcellscpp.write_level_set_vtu +ho_cut = _cutcellscpp.ho_cut +HOCutResult = _cutcellscpp.HOCutResult +HOMeshPart = _cutcellscpp.HOMeshPart + +from .mesh_utils import ( + cutmesh_to_pyvista, + mesh_from_pyvista, + rectangle_triangle_mesh, + rectangle_quad_mesh, + safe_part_name, + structured_triangle_mesh_view, + box_tetrahedron_mesh, + box_hex_mesh, +) + +try: + from . import triangulation_analysis as triangulation_analysis + from .triangulation_analysis import ( + CellCase, + analyze_all_cases, + analyze_single_cell_case, + classify_new_triangulation_edges, + classify_new_triangulation_simplices, + enumerate_cases, + single_cell_level_set, + single_cell_mesh, + summarize_analysis, + ) +except (ModuleNotFoundError, ImportError) as exc: + if isinstance(exc, ModuleNotFoundError) and exc.name != f"{__name__}.triangulation_analysis": + raise diff --git a/python/cutcells/mesh_utils.py b/python/cutcells/mesh_utils.py new file mode 100644 index 0000000..abd0b3d --- /dev/null +++ b/python/cutcells/mesh_utils.py @@ -0,0 +1,221 @@ +"""Structured mesh generation and pyvista ↔ MeshView conversion utilities.""" + +import numpy as np + +try: + import pyvista as pv +except ImportError: + pv = None + + +# --------------------------------------------------------------------------- +# VTK cell-type → topological dimension lookup +# --------------------------------------------------------------------------- + +_VTK_TDIM = { + 3: 1, # VTK_LINE + 5: 2, # VTK_TRIANGLE + 9: 2, # VTK_QUAD + 10: 3, # VTK_TETRA + 12: 3, # VTK_HEXAHEDRON + 13: 3, # VTK_WEDGE (prism) + 14: 3, # VTK_PYRAMID +} + + +# --------------------------------------------------------------------------- +# pyvista ↔ MeshView +# --------------------------------------------------------------------------- + + +def mesh_from_pyvista(grid, *, tdim=None): + """Create a cutcells.MeshView from a pyvista UnstructuredGrid. + + Parameters + ---------- + grid : pyvista.UnstructuredGrid + Source mesh. + tdim : int, optional + Topological dimension. If None, inferred from VTK cell types. + + Returns + ------- + cutcells.MeshView + """ + from . import MeshView + + if pv is None: + raise ImportError("pyvista is required for mesh_from_pyvista") + + coordinates = np.asarray(grid.points, dtype=np.float64) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offsets = np.asarray(grid.offset, dtype=np.int32) + cell_types_np = np.asarray(grid.celltypes, dtype=np.int32) + + if tdim is None: + first_type = int(cell_types_np[0]) + tdim = _VTK_TDIM.get(first_type) + if tdim is None: + raise ValueError( + f"Cannot infer tdim from VTK cell type {first_type}. " + "Pass tdim explicitly." + ) + + return MeshView( + coordinates=coordinates, + connectivity=connectivity, + offsets=offsets, + cell_types=cell_types_np, + tdim=tdim, + ) + + +def safe_part_name(expr): + """Return a filesystem-safe compact name for a level-set selection.""" + return ( + str(expr) + .replace(" ", "") + .replace("<", "lt") + .replace(">", "gt") + .replace("=", "eq") + ) + + +def cutmesh_to_pyvista(cut_mesh): + """Convert a cutcells CutMesh to a pyvista.UnstructuredGrid.""" + if pv is None: + raise ImportError("pyvista is required for cutmesh_to_pyvista") + + points = np.asarray(cut_mesh.vertex_coords, dtype=np.float64) + if points.ndim == 2 and points.shape[1] == 2: + points = np.column_stack([points, np.zeros(points.shape[0])]) + + return pv.UnstructuredGrid( + np.asarray(cut_mesh.cells, dtype=np.int64), + np.asarray(cut_mesh.vtk_types, dtype=np.uint8), + points, + ) + + +def structured_triangle_mesh_view(x0, y0, x1, y1, nx, ny): + """Pure NumPy structured triangular MeshView on [x0,x1] x [y0,y1]. + + Each quadrilateral grid cell is split along the bottom-left to top-right + diagonal. This helper does not require pyvista. + """ + from . import MeshView + + x = np.linspace(x0, x1, num=nx) + y = np.linspace(y0, y1, num=ny) + xx, yy = np.meshgrid(x, y) + coordinates = np.column_stack([xx.ravel(), yy.ravel()]).astype(np.float64) + + connectivity = [] + cell_types = [] + for j in range(ny - 1): + for i in range(nx - 1): + v0 = j * nx + i + v1 = v0 + 1 + v3 = v0 + nx + v2 = v3 + 1 + connectivity.extend([v0, v1, v2]) + connectivity.extend([v0, v2, v3]) + cell_types.extend([5, 5]) # VTK_TRIANGLE + + connectivity = np.asarray(connectivity, dtype=np.int32) + offsets = np.arange(0, connectivity.size + 1, 3, dtype=np.int32) + cell_types = np.asarray(cell_types, dtype=np.int32) + + return MeshView(coordinates, connectivity, offsets, cell_types, tdim=2) + + +# --------------------------------------------------------------------------- +# 2D structured meshes +# --------------------------------------------------------------------------- + + +def rectangle_triangle_mesh(x0, y0, x1, y1, nx, ny): + """Structured triangular mesh on [x0,x1] × [y0,y1]. + + Creates a regular grid of nx × ny points and triangulates it with + Delaunay. Returns a pyvista UnstructuredGrid. + """ + if pv is None: + raise ImportError("pyvista is required") + x = np.linspace(x0, x1, num=nx) + y = np.linspace(y0, y1, num=ny) + xx, yy, zz = np.meshgrid(x, y, [0.0]) + points = np.c_[xx.ravel(), yy.ravel(), zz.ravel()] + poly = pv.PolyData(points).delaunay_2d() + return pv.UnstructuredGrid(poly) + + +def rectangle_quad_mesh(x0, y0, x1, y1, nx, ny): + """Structured quadrilateral mesh on [x0,x1] × [y0,y1]. + + Returns a pyvista UnstructuredGrid with (nx-1)*(ny-1) quads. + """ + if pv is None: + raise ImportError("pyvista is required") + x = np.linspace(x0, x1, num=nx) + y = np.linspace(y0, y1, num=ny) + xx, yy = np.meshgrid(x, y) + points = np.c_[xx.ravel(), yy.ravel(), np.zeros(nx * ny)] + + cells = [] + celltypes = [] + for j in range(ny - 1): + for i in range(nx - 1): + v0 = j * nx + i + v1 = v0 + 1 + v2 = v1 + nx + v3 = v0 + nx + cells.extend([4, v0, v1, v2, v3]) + celltypes.append(9) # VTK_QUAD + + return pv.UnstructuredGrid( + np.array(cells, dtype=np.int64), + np.array(celltypes, dtype=np.uint8), + points, + ) + + +# --------------------------------------------------------------------------- +# 3D structured meshes +# --------------------------------------------------------------------------- + + +def box_tetrahedron_mesh(x0, y0, z0, x1, y1, z1, nx, ny, nz): + """Structured tetrahedral mesh on [x0,x1] × [y0,y1] × [z0,z1]. + + Creates a regular grid and uses Delaunay tetrahedralization. + Returns a pyvista UnstructuredGrid. + """ + if pv is None: + raise ImportError("pyvista is required") + x = np.linspace(x0, x1, num=nx) + y = np.linspace(y0, y1, num=ny) + z = np.linspace(z0, z1, num=nz) + xx, yy, zz = np.meshgrid(x, y, z) + points = np.c_[xx.ravel(), yy.ravel(), zz.ravel()] + poly = pv.PolyData(points).delaunay_3d() + return pv.UnstructuredGrid(poly) + + +def box_hex_mesh(x0, y0, z0, x1, y1, z1, nx, ny, nz): + """Structured hexahedral mesh on [x0,x1] × [y0,y1] × [z0,z1]. + + Returns a pyvista UnstructuredGrid with (nx-1)*(ny-1)*(nz-1) hexahedra. + """ + if pv is None: + raise ImportError("pyvista is required") + sg = pv.ImageData( + dimensions=(nx, ny, nz), + origin=(x0, y0, z0), + spacing=( + (x1 - x0) / (nx - 1), + (y1 - y0) / (ny - 1), + (z1 - z0) / (nz - 1), + ), + ) + return sg.cast_to_unstructured_grid() diff --git a/python/cutcells/wrapper.cpp b/python/cutcells/wrapper.cpp index 7cf07f2..6923671 100644 --- a/python/cutcells/wrapper.cpp +++ b/python/cutcells/wrapper.cpp @@ -6,18 +6,44 @@ #include +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include -#include -#include -#include +#include "../../cpp/src/cell_types.h" +#include "../../cpp/src/cut_cell.h" +#include "../../cpp/src/cut_mesh.h" +#include "../../cpp/src/write_vtk.h" +#include "../../cpp/src/bernstein.h" +#include "../../cpp/src/mapping.h" +#include "../../cpp/src/quadrature.h" +#include "../../cpp/src/quadrature_tables.h" +#include "../../cpp/src/mesh_view.h" +#include "../../cpp/src/level_set.h" +#include "../../cpp/src/adapt_cell.h" +#include "../../cpp/src/cell_topology.h" +#include "../../cpp/src/level_set_cell.h" +#include "../../cpp/src/reference_cell.h" +#include "../../cpp/src/triangulation.h" +#include "../../cpp/src/edge_certification.h" +#include "../../cpp/src/cell_certification.h" +#include "../../cpp/src/refine_cell.h" +#include "../../cpp/src/ho_cut_mesh.h" +#include "../../cpp/src/ho_mesh_part_output.h" namespace nb = nanobind; @@ -25,6 +51,12 @@ using namespace cutcells; namespace { +template +T default_sqrt_epsilon_tol() +{ + return std::sqrt(std::numeric_limits::epsilon()); +} + const std::string& cell_domain_to_str(cell::domain domain_id) { static const std::map type_to_name @@ -52,13 +84,14 @@ auto as_nbarray(V&& x, std::size_t ndim, const std::size_t* shape) template auto as_nbarray(V&& x, const std::initializer_list shape) { - return as_nbarray(x, shape.size(), shape.begin()); + return as_nbarray(std::forward(x), shape.size(), shape.begin()); } template auto as_nbarray(V&& x) { - return as_nbarray(std::move(x), {x.size()}); + const std::size_t size = x.size(); + return as_nbarray(std::forward(x), {size}); } template @@ -67,10 +100,1262 @@ auto as_nbarrayp(std::pair>&& x) return as_nbarray(std::move(x.first), x.second.size(), x.second.data()); } +template +using ndarray1 = nb::ndarray, nb::c_contig>; + +template +using ndarray2 = nb::ndarray, nb::c_contig>; + +std::shared_ptr make_owner_from_objects(nb::object a = nb::object(), + nb::object b = nb::object(), + nb::object c = nb::object(), + nb::object d = nb::object()) +{ + struct Owner + { + nb::object a, b, c, d; + }; + auto owner = std::make_shared(); + owner->a = std::move(a); + owner->b = std::move(b); + owner->c = std::move(c); + owner->d = std::move(d); + return owner; +} + +// Convert CSR connectivity+offset arrays into VTK packed cells layout +// [n0, v0_0, v0_1, ..., n1, v1_0, ...]. +static std::vector csr_to_vtk_cells_impl(std::span connectivity, + std::span offsets) +{ + std::vector out; + if (offsets.size() == 0) + return out; + const std::size_t ncells = (offsets.size() > 0) ? (offsets.size() - 1) : 0; + out.reserve(connectivity.size() + ncells); + for (std::size_t i = 0; i < ncells; ++i) + { + int start = offsets[i]; + int end = offsets[i + 1]; + int n = end - start; + out.push_back(n); + for (int j = start; j < end; ++j) + out.push_back(connectivity[static_cast(j)]); + } + return out; +} + +/// Variant of csr_to_vtk_cells_impl for CutMesh data stored in basix ordering. +/// Non-simplex cells are permuted from basix to VTK vertex ordering. +static std::vector csr_to_vtk_cells_basix_impl( + std::span connectivity, + std::span offsets, + std::span types) +{ + std::vector out; + if (offsets.size() == 0) + return out; + const std::size_t ncells = offsets.size() - 1; + out.reserve(connectivity.size() + ncells); + for (std::size_t i = 0; i < ncells; ++i) + { + const int start = offsets[i]; + const int end = offsets[i + 1]; + const int n = end - start; + out.push_back(n); + const cutcells::cell::type ctype = types[i]; + if (ctype == cutcells::cell::type::point + || ctype == cutcells::cell::type::interval + || ctype == cutcells::cell::type::triangle + || ctype == cutcells::cell::type::tetrahedron) + { + for (int j = start; j < end; ++j) + out.push_back(connectivity[static_cast(j)]); + } + else + { + const auto perm = cutcells::cell::basix_to_vtk_vertex_permutation(ctype); + for (int j = 0; j < n; ++j) + out.push_back(connectivity[static_cast( + start + perm[static_cast(j)])]); + } + } + return out; +} + +template +cutcells::MeshView make_mesh_view_from_numpy( + const ndarray2& coordinates, + const ndarray1& connectivity, + const ndarray1& offsets, + const std::optional>& cell_types, + int tdim) +{ + cutcells::MeshView mesh; + mesh.gdim = static_cast(coordinates.shape(1)); + mesh.tdim = tdim; + + mesh.coordinates = std::span(coordinates.data(), + static_cast(coordinates.size())); + mesh.connectivity = std::span(connectivity.data(), + static_cast(connectivity.size())); + mesh.offsets = std::span(offsets.data(), + static_cast(offsets.size())); + + // Keep-alive bundle: holds numpy arrays and the converted cell-type vector. + struct MeshViewOwner + { + nb::object coords, conn, offs, types_numpy; + std::vector cell_types_converted; + }; + auto owner_data = std::make_shared(); + owner_data->coords = nb::cast(coordinates); + owner_data->conn = nb::cast(connectivity); + owner_data->offs = nb::cast(offsets); + + if (cell_types.has_value()) + { + // Convert VTK integer codes to cutcells cell::type enum at the Python boundary. + const int* raw = cell_types->data(); + const std::size_t ncells = static_cast(cell_types->size()); + owner_data->types_numpy = nb::cast(*cell_types); + owner_data->cell_types_converted.reserve(ncells); + for (std::size_t i = 0; i < ncells; ++i) + owner_data->cell_types_converted.push_back( + cutcells::cell::map_vtk_type_to_cell_type( + static_cast(raw[i]))); + mesh.cell_types = std::span( + owner_data->cell_types_converted.data(), ncells); + // Connectivity from Python/VTK uses VTK vertex ordering. + mesh.vtk_vertex_order = true; + } + + mesh.owner = owner_data; + return mesh; +} + +template +std::vector parent_cell_vertex_coords_vtk( + const cutcells::MeshView& mesh, int cell_id) +{ + const auto ctype = mesh.cell_type(cell_id); + const int nv = cutcells::cell::get_num_vertices(ctype); + std::vector coords(static_cast(nv * mesh.gdim), T(0)); + + for (int vtk_v = 0; vtk_v < nv; ++vtk_v) + { + const int local_v = mesh.vtk_vertex_order + ? vtk_v + : cutcells::cell::vtk_to_basix_vertex(ctype, vtk_v); + const int node_id = mesh.cell_node(cell_id, local_v); + const T* x = mesh.node(node_id); + for (int d = 0; d < mesh.gdim; ++d) + coords[static_cast(vtk_v * mesh.gdim + d)] = x[d]; + } + return coords; +} + +template +bool vertex_is_zero_for_level_set(const cutcells::AdaptCell& adapt_cell, + int vertex_id, + int level_set_id) +{ + const std::uint64_t bit = std::uint64_t(1) << level_set_id; + return (adapt_cell.zero_mask_per_vertex[static_cast(vertex_id)] & bit) != 0; +} + +struct SelectedEntity +{ + cutcells::cell::type type = cutcells::cell::type::point; + std::vector vertices; +}; + +template +std::vector part_selected_entities( + const cutcells::HOMeshPart& part, + const cutcells::AdaptCell& adapt_cell) +{ + if (part.expr.clauses.size() != 1 || part.expr.clauses.front().level_set_index != 0) + { + throw std::runtime_error( + "HOMeshPart direct straight output currently supports only " + "one-clause single-level-set selections"); + } + + const auto relation = part.expr.clauses.front().relation; + std::vector entities; + + if (part.dim == adapt_cell.tdim) + { + if (relation == cutcells::Relation::EqualTo) + throw std::runtime_error("HOMeshPart: phi = 0 is not a volume selection"); + + if (adapt_cell.cell_cert_tag_num_level_sets <= 0) + throw std::runtime_error("HOMeshPart: missing cell certification tags"); + + const auto target = + (relation == cutcells::Relation::LessThan) + ? cutcells::CellCertTag::negative + : cutcells::CellCertTag::positive; + + const int n_cells = adapt_cell.n_entities(adapt_cell.tdim); + entities.reserve(static_cast(n_cells)); + for (int c = 0; c < n_cells; ++c) + { + if (adapt_cell.get_cell_cert_tag(/*level_set_id=*/0, c) == target) + { + auto verts = adapt_cell.entity_to_vertex[adapt_cell.tdim][static_cast(c)]; + SelectedEntity entity; + entity.type = adapt_cell.entity_types[adapt_cell.tdim][static_cast(c)]; + entity.vertices.assign(verts.begin(), verts.end()); + entities.push_back(std::move(entity)); + } + } + return entities; + } + + if (relation != cutcells::Relation::EqualTo) + { + throw std::runtime_error( + "HOMeshPart: lower-dimensional direct export currently supports only phi = 0"); + } + + if (part.dim < 1 || part.dim >= adapt_cell.tdim) + { + throw std::runtime_error("HOMeshPart: unsupported selection dimension"); + } + + if (part.dim != adapt_cell.tdim - 1) + { + throw std::runtime_error( + "HOMeshPart direct straight output currently supports only codim-1 phi = 0 selections"); + } + + std::map, SelectedEntity> unique_entities; + const int n_cells = adapt_cell.n_entities(adapt_cell.tdim); + for (int c = 0; c < n_cells; ++c) + { + const auto cell_type = adapt_cell.entity_types[adapt_cell.tdim][static_cast(c)]; + auto cell_verts = adapt_cell.entity_to_vertex[adapt_cell.tdim][static_cast(c)]; + + if (adapt_cell.tdim == 2) + { + for (const auto& edge : cutcells::cell::edges(cell_type)) + { + SelectedEntity entity; + entity.type = cutcells::cell::type::interval; + entity.vertices = { + static_cast(cell_verts[static_cast(edge[0])]), + static_cast(cell_verts[static_cast(edge[1])])}; + + bool all_zero = true; + for (int gv : entity.vertices) + { + if (!vertex_is_zero_for_level_set(adapt_cell, gv, /*level_set_id=*/0)) + { + all_zero = false; + break; + } + } + if (!all_zero) + continue; + + auto key = entity.vertices; + std::sort(key.begin(), key.end()); + unique_entities.try_emplace(std::move(key), std::move(entity)); + } + continue; + } + + const int n_faces = cutcells::cell::num_faces(cell_type); + for (int fi = 0; fi < n_faces; ++fi) + { + auto local_face = cutcells::cell::face_vertices(cell_type, fi); + SelectedEntity entity; + entity.type = cutcells::cell::face_type(cell_type, fi); + entity.vertices.reserve(local_face.size()); + for (auto lv : local_face) + { + entity.vertices.push_back( + static_cast(cell_verts[static_cast(lv)])); + } + + bool all_zero = true; + for (int gv : entity.vertices) + { + if (!vertex_is_zero_for_level_set(adapt_cell, gv, /*level_set_id=*/0)) + { + all_zero = false; + break; + } + } + if (!all_zero) + continue; + + auto key = entity.vertices; + std::sort(key.begin(), key.end()); + unique_entities.try_emplace(std::move(key), std::move(entity)); + } + } + + entities.reserve(unique_entities.size()); + for (auto& [key, entity] : unique_entities) + entities.push_back(std::move(entity)); + return entities; +} + +inline bool cell_type_is_simplex(cutcells::cell::type cell_type) +{ + using cutcells::cell::type; + return cell_type == type::interval + || cell_type == type::triangle + || cell_type == type::tetrahedron; +} + +inline cutcells::cell::type simplex_type_for_dim(int dim) +{ + using cutcells::cell::type; + switch (dim) + { + case 1: + return type::interval; + case 2: + return type::triangle; + case 3: + return type::tetrahedron; + default: + throw std::runtime_error("Unsupported simplex dimension"); + } +} + +template +std::vector entity_reference_coords(const cutcells::AdaptCell& adapt_cell, + std::span entity_vertices) +{ + std::vector coords( + static_cast(entity_vertices.size() * adapt_cell.tdim), T(0)); + + for (std::size_t j = 0; j < entity_vertices.size(); ++j) + { + const int gv = entity_vertices[j]; + for (int d = 0; d < adapt_cell.tdim; ++d) + { + coords[static_cast(j * adapt_cell.tdim + d)] = + adapt_cell.vertex_coords[static_cast(gv * adapt_cell.tdim + d)]; + } + } + + return coords; +} + +template +std::vector push_forward_parent_reference_coords( + cutcells::cell::type parent_cell_type, + const std::vector& parent_vertex_coords_vtk, + int parent_tdim, + std::span reference_coords) +{ + std::vector physical_coords(reference_coords.size(), T(0)); + cutcells::cell::push_forward_affine( + parent_cell_type, + parent_vertex_coords_vtk, + parent_tdim, + reference_coords, + std::span(physical_coords.data(), physical_coords.size())); + return physical_coords; +} + +template +void gather_subcell_vertices(std::span coords, + int coord_dim, + std::span vertex_ids, + std::vector& out) +{ + out.resize(static_cast(vertex_ids.size() * coord_dim)); + for (std::size_t j = 0; j < vertex_ids.size(); ++j) + { + const int local_v = vertex_ids[j]; + for (int d = 0; d < coord_dim; ++d) + { + out[static_cast(j * coord_dim + d)] = + coords[static_cast(local_v * coord_dim + d)]; + } + } +} + +template +void map_canonical_to_subcell_points(const T* canonical_points, + int num_points, + int simplex_dim, + const T* subcell_vertices, + int parent_tdim, + T* out_points) +{ + const T* v0 = subcell_vertices; + + for (int q = 0; q < num_points; ++q) + { + const T* X = canonical_points + q * simplex_dim; + T* x = out_points + q * parent_tdim; + + for (int d = 0; d < parent_tdim; ++d) + x[d] = v0[d]; + + for (int i = 1; i <= simplex_dim; ++i) + { + const T* vi = subcell_vertices + i * parent_tdim; + for (int d = 0; d < parent_tdim; ++d) + x[d] += X[i - 1] * (vi[d] - v0[d]); + } + } +} + +template +T simplex_physical_measure(const T* vertices, + int simplex_dim, + int gdim) +{ + T J[9] = {}; + const T* v0 = vertices; + + for (int col = 0; col < simplex_dim; ++col) + { + const T* vi = vertices + (col + 1) * gdim; + for (int row = 0; row < gdim; ++row) + J[col * gdim + row] = vi[row] - v0[row]; + } + + if (simplex_dim == gdim) + { + if (simplex_dim == 1) + return std::abs(J[0]); + if (simplex_dim == 2) + return std::abs(J[0] * J[3] - J[2] * J[1]); + + const T det = + J[0] * (J[4] * J[8] - J[7] * J[5]) + - J[3] * (J[1] * J[8] - J[7] * J[2]) + + J[6] * (J[1] * J[5] - J[4] * J[2]); + return std::abs(det); + } + + T G[9] = {}; + for (int i = 0; i < simplex_dim; ++i) + { + for (int j = 0; j < simplex_dim; ++j) + { + T sum = 0; + for (int k = 0; k < gdim; ++k) + sum += J[i * gdim + k] * J[j * gdim + k]; + G[i * simplex_dim + j] = sum; + } + } + + if (simplex_dim == 1) + return std::sqrt(G[0]); + if (simplex_dim == 2) + return std::sqrt(G[0] * G[3] - G[1] * G[2]); + + const T det = + G[0] * (G[4] * G[8] - G[7] * G[5]) + - G[3] * (G[1] * G[8] - G[7] * G[2]) + + G[6] * (G[1] * G[5] - G[4] * G[2]); + return std::sqrt(det); +} + +template +void append_mesh_entity(cutcells::mesh::CutMesh& out, + std::span physical_coords, + int gdim, + cutcells::cell::type cell_type, + int parent_cell_id, + bool triangulate) +{ + if (out._gdim == 0) + out._gdim = gdim; + if (out._tdim == 0) + out._tdim = cutcells::cell::get_tdim(cell_type); + + const int nv = static_cast(physical_coords.size()) / gdim; + const int vertex_base = out._num_vertices; + out._vertex_coords.insert( + out._vertex_coords.end(), physical_coords.begin(), physical_coords.end()); + out._num_vertices += nv; + + if (triangulate && !cell_type_is_simplex(cell_type)) + { + std::vector local_ids(static_cast(nv)); + std::iota(local_ids.begin(), local_ids.end(), 0); + + std::vector> simplices; + cutcells::cell::triangulation(cell_type, local_ids.data(), simplices); + const auto simplex_type = + simplex_type_for_dim(cutcells::cell::get_tdim(cell_type)); + + for (const auto& simplex : simplices) + { + for (int lv : simplex) + out._connectivity.push_back(vertex_base + lv); + out._offset.push_back(static_cast(out._connectivity.size())); + out._types.push_back(simplex_type); + out._parent_map.push_back(parent_cell_id); + out._num_cells += 1; + } + return; + } + + for (int lv = 0; lv < nv; ++lv) + out._connectivity.push_back(vertex_base + lv); + out._offset.push_back(static_cast(out._connectivity.size())); + out._types.push_back(cell_type); + out._parent_map.push_back(parent_cell_id); + out._num_cells += 1; +} + +template +void append_simplex_quadrature(cutcells::quadrature::QuadratureRules& rules, + cutcells::cell::type simplex_type, + std::span ref_vertices, + std::span physical_vertices, + int parent_tdim, + int gdim, + int order) +{ + const auto ref_rule = cutcells::quadrature::get_reference_rule(simplex_type, order); + const int num_points = ref_rule._num_points; + const int simplex_dim = ref_rule._tdim; + + std::vector mapped_ref_points(static_cast(num_points * parent_tdim), T(0)); + map_canonical_to_subcell_points( + ref_rule._points.data(), + num_points, + simplex_dim, + ref_vertices.data(), + parent_tdim, + mapped_ref_points.data()); + + rules._points.insert( + rules._points.end(), mapped_ref_points.begin(), mapped_ref_points.end()); + + const T measure = simplex_physical_measure( + physical_vertices.data(), simplex_dim, gdim); + for (int q = 0; q < num_points; ++q) + rules._weights.push_back(ref_rule._weights[q] * measure); +} + +template +void append_entity_quadrature(cutcells::quadrature::QuadratureRules& rules, + cutcells::cell::type cell_type, + std::span ref_vertices, + std::span physical_vertices, + int parent_tdim, + int gdim, + int parent_cell_id, + int order, + bool triangulate) +{ + if (rules._tdim == 0) + rules._tdim = parent_tdim; + if (rules._offset.empty()) + rules._offset.push_back(0); + + const int cell_dim = cutcells::cell::get_tdim(cell_type); + if (triangulate && !cell_type_is_simplex(cell_type)) + { + const int nv = static_cast(ref_vertices.size()) / parent_tdim; + std::vector local_ids(static_cast(nv)); + std::iota(local_ids.begin(), local_ids.end(), 0); + + std::vector> simplices; + cutcells::cell::triangulation(cell_type, local_ids.data(), simplices); + const auto simplex_type = simplex_type_for_dim(cell_dim); + + std::vector ref_simplex; + std::vector phys_simplex; + for (const auto& simplex : simplices) + { + gather_subcell_vertices( + ref_vertices, + parent_tdim, + std::span(simplex.data(), simplex.size()), + ref_simplex); + gather_subcell_vertices( + physical_vertices, + gdim, + std::span(simplex.data(), simplex.size()), + phys_simplex); + append_simplex_quadrature( + rules, + simplex_type, + std::span(ref_simplex.data(), ref_simplex.size()), + std::span(phys_simplex.data(), phys_simplex.size()), + parent_tdim, + gdim, + order); + } + } + else if (cell_type_is_simplex(cell_type)) + { + append_simplex_quadrature( + rules, + cell_type, + ref_vertices, + physical_vertices, + parent_tdim, + gdim, + order); + } + else + { + const auto ref_rule = cutcells::quadrature::get_reference_rule(cell_type, order); + const T measure = cutcells::cell::affine_volume_factor( + cell_type, physical_vertices.data(), gdim); + rules._points.insert( + rules._points.end(), ref_rule._points.begin(), ref_rule._points.end()); + for (int q = 0; q < ref_rule._num_points; ++q) + rules._weights.push_back(ref_rule._weights[q] * measure); + } + + rules._parent_map.push_back(parent_cell_id); + rules._offset.push_back(static_cast(rules._weights.size())); +} + +inline bool part_mode_is_cut_only(std::string_view mode) +{ + if (mode == "cut_only") + return true; + if (mode == "full") + return false; + throw std::runtime_error("HOMeshPart mode must be 'cut_only' or 'full'"); +} + +template +cutcells::mesh::CutMesh part_visualization_mesh( + const cutcells::HOMeshPart& part, + std::string_view mode) +{ + const bool cut_only = part_mode_is_cut_only(mode); + return cutcells::output::visualization_mesh( + part, /*include_uncut_cells=*/!cut_only); +} + +template +cutcells::quadrature::QuadratureRules part_quadrature( + const cutcells::HOMeshPart& part, + int order, + std::string_view mode) +{ + const bool cut_only = part_mode_is_cut_only(mode); + return cutcells::output::quadrature_rules( + part, order, /*include_uncut_cells=*/!cut_only); +} + +template +void part_write_vtu(const cutcells::HOMeshPart& part, + const std::string& filename, + std::string_view mode) +{ + auto vis = part_visualization_mesh(part, mode); + cutcells::io::write_vtk(filename, vis); +} + +template +void declare_meshview_and_levelset(nb::module_& m, const std::string& suffix) +{ + using MeshViewT = cutcells::MeshView; + using LevelSetMeshDataT = cutcells::LevelSetMeshData; + using LevelSetT = cutcells::LevelSetFunction; + + const std::string mesh_name = "MeshView_" + suffix; + nb::class_(m, mesh_name.c_str(), "Lightweight mesh view") + .def( + "__init__", + [](MeshViewT* self, + const ndarray2& coordinates, + const ndarray1& connectivity, + const ndarray1& offsets, + nb::object cell_types_obj, + int tdim) + { + std::optional> cell_types; + if (!cell_types_obj.is_none()) + cell_types = nb::cast>(cell_types_obj); + + new (self) MeshViewT( + make_mesh_view_from_numpy(coordinates, connectivity, offsets, cell_types, tdim)); + }, + nb::arg("coordinates"), + nb::arg("connectivity"), + nb::arg("offsets"), + nb::arg("cell_types") = nb::none(), + nb::arg("tdim")) + .def_prop_ro("gdim", [](const MeshViewT& self) { return self.gdim; }) + .def_prop_ro("tdim", [](const MeshViewT& self) { return self.tdim; }) + .def("num_nodes", &MeshViewT::num_nodes) + .def("num_cells", &MeshViewT::num_cells) + .def("has_cell_types", &MeshViewT::has_cell_types) + .def("cell_num_nodes", &MeshViewT::cell_num_nodes) + .def("cell_node", &MeshViewT::cell_node) + .def( + "node", + [](const MeshViewT& self, int node_id) + { + const T* x = self.node(node_id); + return nb::ndarray( + x, {static_cast(self.gdim)}, nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "coordinates", + [](const MeshViewT& self) + { + return nb::ndarray( + self.coordinates.data(), + {static_cast(self.num_nodes()), + static_cast(self.gdim)}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "connectivity", + [](const MeshViewT& self) + { + return nb::ndarray( + self.connectivity.data(), + {self.connectivity.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "offsets", + [](const MeshViewT& self) + { + return nb::ndarray( + self.offsets.data(), + {self.offsets.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_types", + [](const MeshViewT& self) + { + if (self.cell_types.empty()) + return nb::ndarray(nullptr, {0}, nb::handle()); + // cell::type has explicit underlying type int; safe to expose as int array. + const int* data = reinterpret_cast(self.cell_types.data()); + return nb::ndarray( + data, + {self.cell_types.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal); + + const std::string ls_mesh_name = "LevelSetMeshData_" + suffix; + nb::class_(m, ls_mesh_name.c_str(), "Discrete level-set mesh data") + .def(nb::init<>()) + .def_prop_ro("gdim", [](const LevelSetMeshDataT& self) { return self.gdim; }) + .def_prop_ro("tdim", [](const LevelSetMeshDataT& self) { return self.tdim; }) + .def_prop_ro("degree", [](const LevelSetMeshDataT& self) { return self.degree; }) + .def("num_dofs", &LevelSetMeshDataT::num_dofs) + .def("num_cells", &LevelSetMeshDataT::num_cells) + .def("cell_num_dofs", &LevelSetMeshDataT::cell_num_dofs, nb::arg("cell_id")) + .def_prop_ro( + "dof_coordinates", + [](const LevelSetMeshDataT& self) + { + const std::size_t gdim = static_cast(self.gdim); + if (gdim == 0) + return nb::ndarray(nullptr, {0, 0}, nb::handle()); + const std::size_t n = self.dof_coordinates.size() / gdim; + return nb::ndarray( + self.dof_coordinates.data(), + {n, gdim}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_dofs", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.cell_dofs.data(), + {self.cell_dofs.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_offsets", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.cell_offsets.data(), + {self.cell_offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_types", + [](const LevelSetMeshDataT& self) + { + // Convert cell::type enum values to int for Python + auto* owner = new std::vector(); + owner->reserve(self.cell_types.size()); + for (auto ct : self.cell_types) + owner->push_back(static_cast(ct)); + return nb::ndarray( + owner->data(), + {owner->size()}, + nb::capsule(owner, [](void* p) noexcept { + delete static_cast*>(p); + })); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "dof_parent_dim", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.dof_parent_dim.data(), + {self.dof_parent_dim.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "dof_parent_id", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.dof_parent_id.data(), + {self.dof_parent_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "dof_parent_param", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.dof_parent_param.data(), + {self.dof_parent_param.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "dof_parent_param_offset", + [](const LevelSetMeshDataT& self) + { + return nb::ndarray( + self.dof_parent_param_offset.data(), + {self.dof_parent_param_offset.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal); + + m.def( + "create_level_set_mesh_data", + [](const MeshViewT& mesh, int degree, T merge_tol) + { + nb::gil_scoped_release release; + return cutcells::create_level_set_mesh_data(mesh, degree, merge_tol); + }, + nb::arg("mesh"), + nb::arg("degree"), + nb::arg("merge_tol") = T(-1)); + + m.def( + "create_level_set_mesh_data", + [](const ndarray2& dof_coordinates, + const ndarray1& cell_dofs, + const ndarray1& cell_offsets, + int degree, + int tdim, + nb::object cell_types_obj) + { + // Convert int cell types to cell::type enum + // Python passes VTK type integers; convert to cell::type. + std::vector cell_types_vec; + if (!cell_types_obj.is_none()) + { + auto arr = nb::cast>(cell_types_obj); + cell_types_vec.reserve(static_cast(arr.size())); + for (std::size_t i = 0; i < static_cast(arr.size()); ++i) + cell_types_vec.push_back( + cutcells::cell::map_vtk_type_to_cell_type( + static_cast(arr.data()[i]))); + } + + std::span cell_types_span; + if (!cell_types_vec.empty()) + cell_types_span = std::span( + cell_types_vec.data(), cell_types_vec.size()); + + return cutcells::create_level_set_mesh_data( + static_cast(dof_coordinates.shape(1)), + tdim, + degree, + std::span(dof_coordinates.data(), + static_cast(dof_coordinates.size())), + std::span(cell_dofs.data(), + static_cast(cell_dofs.size())), + std::span(cell_offsets.data(), + static_cast(cell_offsets.size())), + cell_types_span); + }, + nb::arg("dof_coordinates"), + nb::arg("cell_dofs"), + nb::arg("cell_offsets"), + nb::arg("degree"), + nb::arg("tdim"), + nb::arg("cell_types") = nb::none()); + + const std::string ls_name = "LevelSetFunction_" + suffix; + nb::class_(m, ls_name.c_str(), "Level-set function") + .def( + "__init__", + [](LevelSetT* self, + nb::object value_obj, + nb::object grad_obj, + nb::object nodal_values_obj, + int gdim) + { + using ValueFn = std::function; + using GradFn = std::function; + + ValueFn value_fn; + GradFn grad_fn; + std::span nodal_values; + nb::object nodal_owner; + + if (!value_obj.is_none()) + { + nb::callable value_callable = nb::cast(value_obj); + + value_fn = [value_callable](const T* x, int cell_id) -> T + { + nb::gil_scoped_acquire gil; + nb::ndarray x_arr(x, {static_cast(3)}, nb::handle()); + try + { + return nb::cast(value_callable(x_arr, cell_id)); + } + catch (const nb::python_error&) + { + return nb::cast(value_callable(x_arr)); + } + }; + } + + if (!grad_obj.is_none()) + { + nb::callable grad_callable = nb::cast(grad_obj); + + grad_fn = [grad_callable](const T* x, int cell_id, T* g) + { + nb::gil_scoped_acquire gil; + nb::ndarray x_arr(x, {static_cast(3)}, nb::handle()); + + nb::object result; + try + { + result = grad_callable(x_arr, cell_id); + } + catch (const nb::python_error&) + { + result = grad_callable(x_arr); + } + + auto grad_arr = nb::cast>(result); + for (std::size_t i = 0; i < grad_arr.size(); ++i) + g[i] = grad_arr(i); + }; + } + + if (!nodal_values_obj.is_none()) + { + auto nodal_array = nb::cast>(nodal_values_obj); + nodal_values = std::span(nodal_array.data(), + static_cast(nodal_array.size())); + nodal_owner = nodal_values_obj; + } + + if (!value_fn && nodal_values.empty()) + throw std::runtime_error("LevelSetFunction requires at least one of value or nodal_values."); + + if (grad_fn && !value_fn) + throw std::runtime_error("LevelSetFunction: grad requires value."); + + new (self) LevelSetT{}; + self->value_fn = std::move(value_fn); + self->grad_fn = std::move(grad_fn); + self->nodal_values = nodal_values; + self->gdim = gdim; + self->owner = make_owner_from_objects(value_obj, grad_obj, nodal_owner); + }, + nb::arg("value") = nb::none(), + nb::arg("grad") = nb::none(), + nb::arg("nodal_values") = nb::none(), + nb::arg("gdim") = 0) + .def_prop_ro("gdim", [](const LevelSetT& self) { return self.gdim; }) + .def("has_value", &LevelSetT::has_value) + .def("has_gradient", &LevelSetT::has_gradient) + .def("has_nodal_values", &LevelSetT::has_nodal_values) + .def("has_mesh_data", &LevelSetT::has_mesh_data) + .def("has_dof_values", &LevelSetT::has_dof_values) + .def( + "value", + [](const LevelSetT& self, const ndarray1& x, int cell_id) + { + return self.value(x.data(), cell_id); + }, + nb::arg("x"), + nb::arg("cell_id") = -1) + .def( + "grad", + [](const LevelSetT& self, const ndarray1& x, int cell_id) + { + std::vector g(static_cast(self.gdim), T(0)); + self.grad(x.data(), cell_id, g.data()); + return nb::ndarray( + g.data(), + {g.size()}, + nb::capsule(new std::vector(std::move(g)), + [](void* p) noexcept { delete static_cast*>(p); })); + }, + nb::arg("x"), + nb::arg("cell_id") = -1) + .def("value_at_node", &LevelSetT::value_at_node) + .def_prop_ro( + "nodal_values", + [](const LevelSetT& self) + { + if (self.nodal_values.empty()) + return nb::ndarray(nullptr, {0}, nb::handle()); + return nb::ndarray( + self.nodal_values.data(), + {self.nodal_values.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "dof_values", + [](const LevelSetT& self) + { + if (self.dof_values.empty()) + return nb::ndarray(nullptr, {0}, nb::handle()); + return nb::ndarray( + self.dof_values.data(), + {self.dof_values.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "mesh_data", + [](const LevelSetT& self) -> nb::object + { + if (!self.mesh_data) + return nb::none(); + return nb::cast(self.mesh_data); + }, + nb::rv_policy::reference_internal); + + m.def( + "create_level_set_function", + [](const LevelSetMeshDataT& mesh_data, const ndarray1& dof_values, + const std::string& name) + { + auto mesh_data_ptr = std::make_shared(mesh_data); + return cutcells::create_level_set_function( + std::move(mesh_data_ptr), + std::span( + dof_values.data(), + static_cast(dof_values.size())), + name); + }, + nb::arg("mesh_data"), + nb::arg("dof_values"), + nb::arg("name") = "phi", + "Create a polynomial LevelSetFunction from mesh_data and global dof values."); + + m.def( + "create_level_set", + [](const MeshViewT& mesh, nb::callable phi, int degree, + const std::string& name) + { + LevelSetMeshDataT mesh_data; + { + nb::gil_scoped_release release; + mesh_data = cutcells::create_level_set_mesh_data(mesh, degree, T(-1)); + } + + const std::size_t num_dofs = static_cast(mesh_data.num_dofs()); + const std::size_t gdim = static_cast(mesh_data.gdim); + // Expose as (ndim, npoints) so x[0] gives all x-coords, etc. + // Underlying storage is row-major (npoints, gdim), so use transposed strides. + const std::size_t shape[2] = {gdim, num_dofs}; + // Strides are in element counts (nanobind/DLPack convention). + // data layout is (num_dofs, gdim) row-major, so to view as (gdim, num_dofs): + // stride[0] = 1 (adjacent coords of the same point) + // stride[1] = gdim (first coord of the next point) + const int64_t strides[2] = {1LL, static_cast(gdim)}; + nb::ndarray x( + mesh_data.dof_coordinates.data(), + 2, shape, nb::handle(), strides); + + // Intentional single batched callback invocation. + nb::object values_obj = phi(x); + auto values = nb::cast>(values_obj); + if (static_cast(values.size()) != num_dofs) + { + throw std::runtime_error( + "create_level_set: callback must return a 1D array with length num_dofs"); + } + + auto mesh_data_ptr = std::make_shared(std::move(mesh_data)); + return cutcells::create_level_set_function( + std::move(mesh_data_ptr), + std::span( + values.data(), + static_cast(values.size())), + name); + }, + nb::arg("mesh"), + nb::arg("phi"), + nb::arg("degree"), + nb::arg("name") = "phi", + "Interpolate a batched callable phi(X) at higher-order level-set dof coordinates."); + + m.def( + "interpolate_level_set", + [](const MeshViewT& mesh, nb::callable phi, int degree, + const std::string& name) + { + LevelSetMeshDataT mesh_data; + { + nb::gil_scoped_release release; + mesh_data = cutcells::create_level_set_mesh_data(mesh, degree, T(-1)); + } + + auto mesh_data_ptr = std::make_shared(std::move(mesh_data)); + const std::size_t num_dofs = static_cast(mesh_data_ptr->num_dofs()); + const std::size_t gdim = static_cast(mesh_data_ptr->gdim); + + // Expose as (num_dofs, gdim), i.e. one point per row (matches tests + docs). + // Keep `mesh_data_ptr` alive via `owner` in case the callback holds onto X. + nb::object owner = nb::cast(mesh_data_ptr); + nb::ndarray X( + mesh_data_ptr->dof_coordinates.data(), + {num_dofs, gdim}, + owner); + + // Intentional single batched callback invocation. + nb::object values_obj = phi(X); + auto values = nb::cast>(values_obj); + if (static_cast(values.size()) != num_dofs) + { + throw std::runtime_error( + "interpolate_level_set: callback must return a 1D array with length num_dofs"); + } + + return cutcells::create_level_set_function( + std::move(mesh_data_ptr), + std::span( + values.data(), + static_cast(values.size())), + name); + }, + nb::arg("mesh"), + nb::arg("phi"), + nb::arg("degree"), + nb::arg("name") = "phi", + "Interpolate a batched callable phi(X) on higher-order level-set DOF coordinates.\n" + "X is passed as an ndarray of shape (num_dofs, gdim)."); + + // ---- cut_mesh_view ---- + // Cuts a MeshView using a LevelSetFunction, returning a CutMesh. + // Nodal level-set values are taken from ls.nodal_values if available, + // otherwise ls.value() is evaluated at every mesh node. + m.def( + "cut_mesh_view", + [](const MeshViewT& mesh, const LevelSetT& ls, + const std::string& cut_type_str, bool triangulate) + { + if (!mesh.has_cell_types()) + throw std::runtime_error( + "cut_mesh_view: MeshView must have cell_types (VTK type IDs)"); + + // Build nodal level-set values while GIL is held + // (ls.value() may call back into Python) + const std::size_t n = static_cast(mesh.num_nodes()); + std::vector ls_vals(n); + if (ls.has_nodal_values()) + { + if (ls.nodal_values.size() != n) + throw std::runtime_error( + "cut_mesh_view: nodal_values size does not match MeshView num_nodes"); + std::copy(ls.nodal_values.begin(), ls.nodal_values.end(), ls_vals.begin()); + } + else if (ls.has_value()) + { + for (std::size_t i = 0; i < n; ++i) + ls_vals[i] = ls.value(mesh.node(static_cast(i)), -1); + } + else + { + throw std::runtime_error( + "cut_mesh_view: LevelSetFunction has neither value nor nodal_values"); + } + + // Cut mesh with GIL released + // Convert cell::type back to VTK int codes for the legacy cut_vtk_mesh API. + std::vector vtk_types_vec; + vtk_types_vec.reserve(mesh.cell_types.size()); + for (auto ct : mesh.cell_types) + vtk_types_vec.push_back( + static_cast(cutcells::cell::map_cell_type_to_vtk(ct))); + + nb::gil_scoped_release release; + return mesh::cut_vtk_mesh( + std::span(ls_vals.data(), ls_vals.size()), + mesh.coordinates, + mesh.connectivity, + mesh.offsets, + std::span(vtk_types_vec.data(), vtk_types_vec.size()), + cut_type_str, + triangulate); + }, + nb::arg("mesh"), + nb::arg("level_set"), + nb::arg("cut_type"), + nb::arg("triangulate") = true, + "Cut a MeshView with a LevelSetFunction.\n" + "Returns a CutMesh containing cells classified by cut_type (\"phi<0\", \"phi=0\", \"phi>0\").\n" + "Level-set values are taken from nodal_values if set, otherwise evaluated via value()."); + + // Simple aliases for Python + if constexpr (std::is_same_v) + { + m.def( + "write_level_set_vtu", + [](const std::string& filename, const LevelSetT& ls, const std::string& field_name) + { + nb::gil_scoped_release release; + io::write_level_set_vtu(filename, ls, field_name); + }, + nb::arg("filename"), + nb::arg("level_set"), + nb::arg("field_name") = "phi"); + + m.attr("MeshView") = m.attr(mesh_name.c_str()); + m.attr("LevelSetMeshData") = m.attr(ls_mesh_name.c_str()); + m.attr("LevelSetFunction") = m.attr(ls_name.c_str()); + } +} + template void declare_float(nb::module_& m, std::string type) { - m.def("classify_cell_domain", [](nb::ndarray& ls_values){ + m.def("classify_cell_domain", [](const nb::ndarray, nb::c_contig>& ls_values){ cell::domain domain_id = cell::classify_cell_domain(std::span{ls_values.data(),static_cast(ls_values.size())}); auto domain_str = cell_domain_to_str(domain_id); return domain_str; @@ -122,31 +1407,55 @@ void declare_float(nb::module_& m, std::string type) .def_prop_ro( "connectivity", [](const cell::CutCell& self) { - nb::list inner; - - for(std::size_t i=0;i, nb::c_contig>( + self._connectivity.data(), + {self._connectivity.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of flat CSR connectivity array.") + .def_prop_ro( + "offsets", + [](const cell::CutCell& self) { + return nb::ndarray, nb::c_contig>( + self._offset.data(), + {self._offset.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of CSR offsets array.") + .def_prop_ro( + "cells", + [](const cell::CutCell& self) { + return as_nbarray(csr_to_vtk_cells_impl( + std::span(self._connectivity.data(), self._connectivity.size()), + std::span(self._offset.data(), self._offset.size()))); + }, + nb::rv_policy::move, + "Packed VTK cells array [n0, v0..., n1, v1..., ...] built from connectivity+offsets.") .def_prop_ro( "types", [](const cell::CutCell& self) { - //allocate memory - nb::list types; - - for(std::size_t i=0;i(map_cell_type_to_vtk(self._types[i]))); - } - - return types; - }) + using type_id_t = std::underlying_type_t; + static_assert(std::is_integral_v, "cell::type must have integral underlying type"); + return nb::ndarray, nb::c_contig>( + reinterpret_cast(self._types.data()), + {self._types.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of cut-cell type ids (cell::type enum underlying values).") + .def_prop_ro( + "vtk_types", + [](const cell::CutCell& self) { + std::vector vtk; + vtk.reserve(self._types.size()); + for (const auto t : self._types) + vtk.push_back(static_cast(cell::map_cell_type_to_vtk(t))); + return as_nbarray(std::move(vtk)); + }, + nb::rv_policy::move, + "VTK type IDs for each sub-cell (uint8), suitable for pv.UnstructuredGrid.") .def_prop_ro( "vertex_parent_entity", [](const cell::CutCell& self) { @@ -158,6 +1467,21 @@ void declare_float(nb::module_& m, std::string type) nb::rv_policy::reference_internal, "Return parent entity token for each cut-cell vertex.\n" "Tokens encode the origin: edge intersections use edge id, original vertices use 100+vid, special points use 200+sid.") + .def_prop_ro( + "vertex_coords_phys", + [](const cell::CutCell& self) { + const std::size_t gdim = static_cast(self._gdim); + if (gdim == 0 || self._vertex_coords_phys.empty()) + return nb::ndarray(nullptr, {0, 0}, nb::handle()); + const std::size_t n = self._vertex_coords_phys.size() / gdim; + return nb::ndarray( + self._vertex_coords_phys.data(), + {n, gdim}, + nb::handle()); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of cut-cell physical vertex coordinates as shape (num_vertices, gdim). " + "Empty until compute_physical_cut_vertices() or complete_from_physical() is called.") .def("str", [](const cell::CutCell& self) {cell::str(self); return ;}) .def("volume", [](const cell::CutCell& self) {return cell::volume(self);}) .def("write_vtk", [](cell::CutCell& self, std::string fname) {io::write_vtk(fname,self); return ;}); @@ -176,16 +1500,15 @@ void declare_float(nb::module_& m, std::string type) .def_prop_ro( "types", [](const mesh::CutCells& self) { - //allocate memory - nb::list types; - - for(std::size_t i=0;i(map_cell_type_to_vtk(self._types[i]))); - } - - return types; - }) + using type_id_t = std::underlying_type_t; + static_assert(std::is_integral_v, "cell::type must have integral underlying type"); + return nb::ndarray, nb::c_contig>( + reinterpret_cast(self._types.data()), + {self._types.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of cut-cell type ids (cell::type enum underlying values).") .def_prop_ro( "parent_map", [](const mesh::CutCells& self) { @@ -203,103 +1526,206 @@ void declare_float(nb::module_& m, std::string type) [](const mesh::CutMesh& self) { const std::size_t gdim = static_cast(self._gdim); if (gdim == 0) - return nb::ndarray(nullptr, {0, 0}, nb::handle()); + return nb::ndarray(nullptr, {0, 0}, nb::handle()); const std::size_t n = self._vertex_coords.size() / gdim; - return nb::ndarray( - self._vertex_coords.data(), - {n, gdim}, - nb::handle()); + // Return an *owned* copy so pyvista (which retains the array) does not + // keep the CutMesh alive via a numpy base reference at interpreter shutdown. + std::vector copy = self._vertex_coords; + return as_nbarray(std::move(copy), {n, gdim}); }, - nb::rv_policy::reference_internal, - "Zero-copy view of mesh vertex coordinates as shape (num_vertices, gdim).") + nb::rv_policy::move, + "Copy of mesh vertex coordinates as shape (num_vertices, gdim).") .def_prop_ro( "connectivity", [](const mesh::CutMesh& self) { - return nb::ndarray(self._connectivity.data(),{self._connectivity.size()}, nb::handle()); + return nb::ndarray(self._connectivity.data(),{self._connectivity.size()}, nb::cast(self, nb::rv_policy::reference)); }, nb::rv_policy::reference_internal, " Return connectivity vector.") .def_prop_ro( "offset", [](const mesh::CutMesh& self) { - return nb::ndarray(self._offset.data(),{self._offset.size()}, nb::handle()); + return nb::ndarray(self._offset.data(),{self._offset.size()}, nb::cast(self, nb::rv_policy::reference)); }, nb::rv_policy::reference_internal, " Return offset vector.") .def_prop_ro( "types", [](const mesh::CutMesh& self) { - //allocate memory - nb::list types; - for(std::size_t i=0;i(map_cell_type_to_vtk(self._types[i]))); - } - return types; - }) + using type_id_t = std::underlying_type_t; + static_assert(std::is_integral_v, "cell::type must have integral underlying type"); + return nb::ndarray, nb::c_contig>( + reinterpret_cast(self._types.data()), + {self._types.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Zero-copy view of cut-mesh type ids (cell::type enum underlying values).") + .def_prop_ro( + "vtk_types", + [](const mesh::CutMesh& self) { + std::vector vtk; + vtk.reserve(self._types.size()); + for (const auto t : self._types) + vtk.push_back(static_cast(cell::map_cell_type_to_vtk(t))); + return as_nbarray(std::move(vtk)); + }, + nb::rv_policy::move, + "VTK type IDs for each sub-cell (uint8), suitable for pv.UnstructuredGrid.") .def_prop_ro( "cells", [](const mesh::CutMesh& self) { - std::size_t num_cells = self._offset.size()-1; - std::vector cells; - - for(std::size_t i=0;i(self._connectivity.data(), self._connectivity.size()), + std::span(self._offset.data(), self._offset.size()), + std::span(self._types.data(), self._types.size()))); }, - " Return cells.") + nb::rv_policy::move, + "Packed cells view [n, v0, ...] with basix-to-VTK vertex permutation applied." + " Suitable for pv.UnstructuredGrid. Allocates on each call.") .def_prop_ro( "parent_map", [](const mesh::CutMesh& self) { - return nb::ndarray(self._parent_map.data(),{self._parent_map.size()}, nb::handle()); + return nb::ndarray(self._parent_map.data(),{self._parent_map.size()}, nb::cast(self, nb::rv_policy::reference)); }, nb::rv_policy::reference_internal, " Return parent map of cut mesh."); + // ---- frame-completion helpers ---- + m.def("compute_physical_cut_vertices", + [](cell::CutCell& cut_cell) { + nb::gil_scoped_release release; + cell::compute_physical_cut_vertices(cut_cell); + }, + nb::arg("cut_cell"), + "Fill _vertex_coords_phys via affine push-forward of _vertex_coords (reference-frame cut path)."); + + m.def("complete_from_physical", + [](cell::CutCell& cut_cell) { + nb::gil_scoped_release release; + cell::complete_from_physical(cut_cell); + }, + nb::arg("cut_cell"), + "Copy vertex_coords to _vertex_coords_phys, then pull back to fill _vertex_coords " + "with parent reference coordinates (physical-frame cut path)."); + + // ---- QuadratureRules class ---- + { + std::string qr_name = "QuadratureRules_" + type; + nb::class_>(m, qr_name.c_str(), + "Flat batch quadrature rules for a collection of cut cells.\n" + "Points are in parent reference space; weights incorporate the physical Jacobian determinant.") + .def(nb::init<>()) + .def_prop_ro("tdim", + [](const quadrature::QuadratureRules& self) { return self._tdim; }, + "Topological dimension of the point coordinates.") + .def_prop_ro("points", + [](const quadrature::QuadratureRules& self) { + // Always return a flat 1-D array; caller reshapes with [:, tdim] if needed. + // Use nb::cast(self, ...) as the ndarray owner so NumPy holds a proper + // strong reference to the parent — avoids keep_alive cycles at shutdown. + return nb::ndarray( + self._points.data(), {self._points.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Quadrature points in parent reference space, flat array of length (total_points * tdim). " + "Reshape to (-1, tdim) to get shape (total_points, tdim).") + .def_prop_ro("weights", + [](const quadrature::QuadratureRules& self) { + return nb::ndarray( + self._weights.data(), {self._weights.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Physical integration weights, shape (total_points,).") + .def_prop_ro("offset", + [](const quadrature::QuadratureRules& self) { + return nb::ndarray( + self._offset.data(), {self._offset.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Offsets into points/weights per cut-cell rule, shape (num_rules+1,).") + .def_prop_ro("parent_map", + [](const quadrature::QuadratureRules& self) { + return nb::ndarray( + self._parent_map.data(), {self._parent_map.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal, + "Index of the originating cut-cell in the input list, shape (num_rules,)."); + } + + // ---- make_quadrature ---- + m.def("make_quadrature", + [](const std::vector>& cut_cells, int order) { + nb::gil_scoped_release release; + return quadrature::make_quadrature(cut_cells, order); + }, + nb::arg("cut_cells"), nb::arg("order"), + "Generate flat quadrature rules for a list of enriched CutCells.\n" + "Both vertex_coords (_vertex_coords, reference) and vertex_coords_phys must be populated on each cell.\n" + "Returns a QuadratureRules_ object."); + m.def("create_cut_mesh", [](mesh::CutCells& cut_cells){ + nb::gil_scoped_release release; return mesh::create_cut_mesh(cut_cells); } , "Creating a cut mesh"); - m.def("cut", [](cell::type cell_type, const nb::ndarray& vertex_coordinates, const int gdim, - const nb::ndarray& ls_values, const std::string& cut_type_str, bool triangulate){ + m.def("cut", [](cell::type cell_type, + const nb::ndarray, nb::c_contig>& vertex_coordinates, + const int gdim, + const nb::ndarray, nb::c_contig>& ls_values, + const std::string& cut_type_str, + bool triangulate){ cell::CutCell cut_cell; + nb::gil_scoped_release release; cell::cut(cell_type, std::span{vertex_coordinates.data(),static_cast(vertex_coordinates.size())}, gdim, std::span{ls_values.data(),static_cast(ls_values.size())}, cut_type_str, cut_cell, triangulate); return cut_cell; } , "cut a cell"); - m.def("higher_order_cut", [](cell::type cell_type, const nb::ndarray& vertex_coordinates, const int gdim, - const nb::ndarray& ls_values, const std::string& cut_type_str, bool triangulate){ + m.def("higher_order_cut", [](cell::type cell_type, + const nb::ndarray, nb::c_contig>& vertex_coordinates, + const int gdim, + const nb::ndarray, nb::c_contig>& ls_values, + const std::string& cut_type_str, + bool triangulate){ + nb::gil_scoped_release release; cell::CutCell cut_cell = cell::higher_order_cut(cell_type, std::span{vertex_coordinates.data(),static_cast(vertex_coordinates.size())}, gdim, std::span{ls_values.data(),static_cast(ls_values.size())}, cut_type_str, triangulate); return cut_cell; } , "cut a second order cell"); - m.def("locate_cells", [](nb::ndarray& ls_vals, nb::ndarray& points, - nb::ndarray& connectivity, nb::ndarray& offset, - nb::ndarray& vtk_type, + m.def("locate_cells", [](const nb::ndarray, nb::c_contig>& ls_vals, + const nb::ndarray, nb::c_contig>& points, + const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offset, + const nb::ndarray, nb::c_contig>& vtk_type, const std::string& cut_type_str){ - return as_nbarray(mesh::locate_cells(std::span(ls_vals.data(),ls_vals.size()), - std::span(points.data(),points.size()), - std::span(connectivity.data(),connectivity.size()), - std::span(offset.data(),offset.size()), - std::span(vtk_type.data(),vtk_type.size()), - cell::string_to_cut_type(cut_type_str))); + std::vector located_cells; + { + nb::gil_scoped_release release; + located_cells = mesh::locate_cells(std::span(ls_vals.data(),ls_vals.size()), + std::span(points.data(),points.size()), + std::span(connectivity.data(),connectivity.size()), + std::span(offset.data(),offset.size()), + std::span(vtk_type.data(),vtk_type.size()), + cell::string_to_cut_type(cut_type_str)); + } + return as_nbarray(std::move(located_cells)); } , "locate cells in vtk mesh"); - m.def("cut_vtk_mesh", [](nb::ndarray& ls_vals, nb::ndarray& points, - nb::ndarray& connectivity, nb::ndarray& offset, - nb::ndarray& vtk_type, + m.def("cut_vtk_mesh", [](const nb::ndarray, nb::c_contig>& ls_vals, + const nb::ndarray, nb::c_contig>& points, + const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offset, + const nb::ndarray, nb::c_contig>& vtk_type, const std::string& cut_type_str, bool triangulate){ + nb::gil_scoped_release release; return mesh::cut_vtk_mesh(std::span(ls_vals.data(),ls_vals.size()), std::span(points.data(),points.size()), std::span(connectivity.data(),connectivity.size()), @@ -311,6 +1737,939 @@ void declare_float(nb::module_& m, std::string type) , nb::arg("ls_vals"), nb::arg("points"), nb::arg("connectivity"), nb::arg("offset"), nb::arg("vtk_type"), nb::arg("cut_type_str"), nb::arg("triangulate") = true , "cut vtk mesh"); + + m.def("runtime_quadrature", + [](const nb::ndarray, nb::c_contig>& ls_vals, + const nb::ndarray, nb::c_contig>& points, + const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offset, + const nb::ndarray, nb::c_contig>& vtk_type, + const std::string& cut_type_str, + bool triangulate, + int order) { + { + nb::gil_scoped_release release; + return quadrature::runtime_quadrature( + std::span(ls_vals.data(), ls_vals.size()), + std::span(points.data(), points.size()), + std::span(connectivity.data(),connectivity.size()), + std::span(offset.data(), offset.size()), + std::span(vtk_type.data(), vtk_type.size()), + cut_type_str, + triangulate, + order); + } + }, + nb::arg("ls_vals"), nb::arg("points"), nb::arg("connectivity"), + nb::arg("offset"), nb::arg("vtk_type"), nb::arg("cut_type_str"), + nb::arg("triangulate") = true, nb::arg("order") = 3, + "Generate flat quadrature rules for all mesh cells in the requested " + "level-set domain.\n" + "Full cells (entirely inside/outside) use a direct reference rule scaled " + "by |det J|.\n" + "Cut cells (intersected) are cut and integrated via append_quadrature.\n" + "Returns a QuadratureRules object with reference-space points and " + "physical weights."); + + m.def("physical_points", + [](const quadrature::QuadratureRules& rules, + const nb::ndarray, nb::c_contig>& points, + const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offset, + const nb::ndarray, nb::c_contig>& vtk_type) { + std::vector pts; + { + nb::gil_scoped_release release; + pts = quadrature::physical_points( + rules, + std::span(points.data(), points.size()), + std::span(connectivity.data(),connectivity.size()), + std::span(offset.data(), offset.size()), + std::span(vtk_type.data(), vtk_type.size())); + } + return as_nbarray(std::move(pts)); + }, + nb::arg("rules"), nb::arg("points"), nb::arg("connectivity"), + nb::arg("offset"), nb::arg("vtk_type"), + "Map reference-space quadrature points to physical space.\n" + "Returns a flat numpy array of shape (total_num_points * 3,)."); +} + +// ---- HO cut types (BackgroundMeshData, HOCutCells, HOMeshPart) ---- + +template +void declare_ho_cut(nb::module_& m, const std::string& type) +{ + using MeshViewT = cutcells::MeshView; + using LevelSetT = cutcells::LevelSetFunction; + using BGDataT = cutcells::BackgroundMeshData; + using HOCutT = cutcells::HOCutCells; + using PartT = cutcells::HOMeshPart; + + // --- Wrapper that owns both HOCutCells + BackgroundMeshData --- + struct HOCutResult + { + HOCutT cut_cells; + BGDataT bg; + std::shared_ptr level_set_owner; + }; + + std::string result_name = "HOCutResult_" + type; + auto py_result = nb::class_(m, result_name.c_str(), + "Result of ho cut(): holds HOCutCells and BackgroundMeshData."); + + py_result + .def_prop_ro("num_cut_cells", + [](const HOCutResult& self) { return self.cut_cells.num_cut_cells(); }) + .def_prop_ro("num_cells", + [](const HOCutResult& self) { return self.bg.num_cells; }) + .def_prop_ro("num_level_sets", + [](const HOCutResult& self) { return self.bg.num_level_sets; }) + .def_prop_ro("level_set_names", + [](const HOCutResult& self) { return self.bg.level_set_names; }) + .def_prop_ro("parent_cell_ids", + [](const HOCutResult& self) { + return nb::ndarray( + self.cut_cells.parent_cell_ids.data(), + {self.cut_cells.parent_cell_ids.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro("active_level_set_mask", + [](const HOCutResult& self) { + return nb::ndarray( + self.cut_cells.active_level_set_mask.data(), + {self.cut_cells.active_level_set_mask.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal, + "Per cut-cell bitmask of level sets intersecting the background cell.") + .def_prop_ro("cell_domains", + [](const HOCutResult& self) { + const int* data = reinterpret_cast( + self.bg.cell_domains.data()); + return nb::ndarray( + data, + {static_cast(self.bg.num_level_sets), + static_cast(self.bg.num_cells)}, + nb::handle()); + }, + nb::rv_policy::reference_internal, + "Per-level-set domain classification, shape (num_level_sets, num_cells).") + .def("__getitem__", + [](const HOCutResult& self, const std::string& expr_str) { + return cutcells::select_part( + self.cut_cells, + self.bg, + std::string_view(expr_str)); + }, + nb::arg("expr"), + nb::keep_alive<0, 1>(), + "Select a mesh part via expression, e.g. result[\"phi < 0\"].") + .def( + "adapt_cell", + [](const HOCutResult& self, int cut_cell_id) -> const cutcells::AdaptCell& + { + if (cut_cell_id < 0 + || cut_cell_id >= static_cast(self.cut_cells.adapt_cells.size())) + { + throw std::out_of_range("adapt_cell: cut_cell_id out of range"); + } + return self.cut_cells.adapt_cells[static_cast(cut_cell_id)]; + }, + nb::arg("cut_cell_id"), + nb::rv_policy::reference_internal, + "Return the AdaptCell for a cut-cell index."); + // --- HOMeshPart --- + std::string part_name = "HOMeshPart_" + type; + nb::class_(m, part_name.c_str(), "Mesh part selected by expression") + .def_prop_ro("dim", + [](const PartT& self) { return self.dim; }) + .def_prop_ro("cut_only", + [](const PartT& self) { return self.cut_only; }) + .def_prop_ro("num_cut_cells", + [](const PartT& self) { + return static_cast(self.cut_cell_ids.size()); + }) + .def_prop_ro("num_uncut_cells", + [](const PartT& self) { + return static_cast(self.uncut_cell_ids.size()); + }) + .def_prop_ro("cut_cell_ids", + [](const PartT& self) { + return nb::ndarray( + self.cut_cell_ids.data(), + {self.cut_cell_ids.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def_prop_ro("uncut_cell_ids", + [](const PartT& self) { + return nb::ndarray( + self.uncut_cell_ids.data(), + {self.uncut_cell_ids.size()}, + nb::handle()); + }, + nb::rv_policy::reference_internal) + .def( + "visualization_mesh", + [](const PartT& self, + const std::string& mode) { + nb::gil_scoped_release release; + return part_visualization_mesh(self, mode); + }, + nb::arg("mode") = "full", + "Return a visualization mesh for an HOMeshPart, preserving AdaptCell topology.") + .def( + "quadrature", + [](const PartT& self, + int order, + const std::string& mode) { + nb::gil_scoped_release release; + return part_quadrature(self, order, mode); + }, + nb::arg("order") = 3, + nb::arg("mode") = "full", + "Return quadrature rules for an HOMeshPart, preserving AdaptCell topology.") + .def( + "write_vtu", + [](const PartT& self, + const std::string& filename, + const std::string& mode) { + nb::gil_scoped_release release; + part_write_vtu(self, filename, mode); + }, + nb::arg("filename"), + nb::arg("mode") = "full", + "Write a straight VTU file for an HOMeshPart."); + + // --- ho_cut() factory --- + m.def("ho_cut", + [](const MeshViewT& mesh, const LevelSetT& ls, bool triangulate) { + nb::gil_scoped_release release; + auto owned_ls = std::make_shared(ls); + auto [hc, bg] = cutcells::cut(mesh, *owned_ls, triangulate); + return HOCutResult{std::move(hc), std::move(bg), owned_ls}; + }, + nb::arg("mesh"), nb::arg("level_set"), nb::arg("triangulate") = false, + "Cut a MeshView with a single LevelSetFunction.\n" + "Returns an HOCutResult; use result[\"phi < 0\"] to select parts."); + + m.def("ho_cut", + [](const MeshViewT& mesh, const std::vector& level_sets, + bool triangulate) { + nb::gil_scoped_release release; + auto owned_ls = std::make_shared>(level_sets); + auto [hc, bg] = cutcells::cut(mesh, *owned_ls, triangulate); + return HOCutResult{std::move(hc), std::move(bg), owned_ls}; + }, + nb::arg("mesh"), nb::arg("level_sets"), nb::arg("triangulate") = true, + "Cut a MeshView with multiple LevelSetFunctions.\n" + "Returns an HOCutResult; use result[\"phi1 < 0 and phi2 = 0\"] to select parts."); + + m.def("cut", + [](const MeshViewT& mesh, const LevelSetT& ls, bool triangulate) { + nb::gil_scoped_release release; + auto owned_ls = std::make_shared(ls); + auto [hc, bg] = cutcells::cut(mesh, *owned_ls, triangulate); + return HOCutResult{std::move(hc), std::move(bg), owned_ls}; + }, + nb::arg("mesh"), nb::arg("level_set"), nb::arg("triangulate") = false, + "Cut a MeshView with a single LevelSetFunction.\n" + "Returns an HOCutResult; use result[\"phi < 0\"] to select parts."); + + m.def("cut", + [](const MeshViewT& mesh, const std::vector& level_sets, + bool triangulate) { + nb::gil_scoped_release release; + auto owned_ls = std::make_shared>(level_sets); + auto [hc, bg] = cutcells::cut(mesh, *owned_ls, triangulate); + return HOCutResult{std::move(hc), std::move(bg), owned_ls}; + }, + nb::arg("mesh"), nb::arg("level_sets"), nb::arg("triangulate") = true, + "Cut a MeshView with multiple LevelSetFunctions.\n" + "Returns an HOCutResult; use result[\"phi1 < 0 and phi2 = 0\"] to select parts."); + + // Simple aliases for Python + if constexpr (std::is_same_v) + { + m.attr("HOCutResult") = m.attr(result_name.c_str()); + m.attr("HOMeshPart") = m.attr(part_name.c_str()); + } +} + +template +void declare_certification(nb::module_& m, const std::string& suffix) +{ + using MeshViewT = cutcells::MeshView; + using LevelSetT = cutcells::LevelSetFunction; + using AdaptCellT = cutcells::AdaptCell; + using LevelSetCellT = cutcells::LevelSetCell; + + const std::string adapt_name = "AdaptCell_" + suffix; + nb::class_(m, adapt_name.c_str(), "Adaptive local cell topology") + .def(nb::init<>()) + .def_prop_ro("tdim", [](const AdaptCellT& self) { return self.tdim; }) + .def_prop_ro("active_level_set_mask", [](const AdaptCellT& self) { return self.active_level_set_mask; }) + .def("num_vertices", &AdaptCellT::n_vertices) + .def("num_edges", [](const AdaptCellT& self) { return self.n_entities(1); }) + .def("num_cells", [](const AdaptCellT& self) { return self.n_entities(self.tdim); }) + .def_prop_ro( + "vertex_coords", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_coords.data(), + {static_cast(self.n_vertices()), + static_cast(self.tdim)}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "vertex_source_edge_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_source_edge_id.data(), + {self.vertex_source_edge_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "vertex_parent_dim", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_parent_dim.data(), + {self.vertex_parent_dim.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "vertex_parent_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_parent_id.data(), + {self.vertex_parent_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "vertex_parent_param", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_parent_param.data(), + {self.vertex_parent_param.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "vertex_parent_param_offset", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.vertex_parent_param_offset.data(), + {self.vertex_parent_param_offset.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_mask_per_vertex", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_mask_per_vertex.data(), + {self.zero_mask_per_vertex.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "negative_mask_per_vertex", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.negative_mask_per_vertex.data(), + {self.negative_mask_per_vertex.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_active_level_set_mask", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.cell_active_level_set_mask.data(), + {self.cell_active_level_set_mask.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "num_zero_entities", + [](const AdaptCellT& self) { return self.n_zero_entities(); }) + .def_prop_ro( + "zero_entity_dim", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_dim.data(), + {self.zero_entity_dim.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_entity_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_id.data(), + {self.zero_entity_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_entity_zero_mask", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_zero_mask.data(), + {self.zero_entity_zero_mask.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_entity_is_owned", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_is_owned.data(), + {self.zero_entity_is_owned.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_entity_parent_dim", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_parent_dim.data(), + {self.zero_entity_parent_dim.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "zero_entity_parent_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.zero_entity_parent_id.data(), + {self.zero_entity_parent_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "edge_connectivity", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[1].indices.data(), + {self.entity_to_vertex[1].indices.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "edge_offsets", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[1].offsets.data(), + {self.entity_to_vertex[1].offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "face_connectivity", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[2].indices.data(), + {self.entity_to_vertex[2].indices.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "face_offsets", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[2].offsets.data(), + {self.entity_to_vertex[2].offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "face_to_cell_connectivity", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.connectivity[2][self.tdim].indices.data(), + {self.connectivity[2][self.tdim].indices.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "face_to_cell_offsets", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.connectivity[2][self.tdim].offsets.data(), + {self.connectivity[2][self.tdim].offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_to_face_connectivity", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.connectivity[self.tdim][2].indices.data(), + {self.connectivity[self.tdim][2].indices.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_to_face_offsets", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.connectivity[self.tdim][2].offsets.data(), + {self.connectivity[self.tdim][2].offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_types", + [](const AdaptCellT& self) + { + std::vector types; + const int n_cells = self.n_entities(self.tdim); + types.reserve(static_cast(n_cells)); + for (int c = 0; c < n_cells; ++c) + types.push_back(static_cast(self.entity_types[self.tdim][static_cast(c)])); + return as_nbarray(std::move(types)); + }, + nb::rv_policy::move) + .def_prop_ro( + "cell_connectivity", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[self.tdim].indices.data(), + {self.entity_to_vertex[self.tdim].indices.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_source_cell_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.cell_source_cell_id.data(), + {self.cell_source_cell_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_refinement_generation", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.cell_refinement_generation.data(), + {self.cell_refinement_generation.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_refinement_reason", + [](const AdaptCellT& self) + { + const auto* data = reinterpret_cast( + self.cell_refinement_reason.data()); + return nb::ndarray( + data, + {self.cell_refinement_reason.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_host_parent_cell_id", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.cell_host_parent_cell_id.data(), + {self.cell_host_parent_cell_id.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def_prop_ro( + "cell_offsets", + [](const AdaptCellT& self) + { + return nb::ndarray( + self.entity_to_vertex[self.tdim].offsets.data(), + {self.entity_to_vertex[self.tdim].offsets.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal) + .def( + "edge_root_tags", + [](const AdaptCellT& self, int level_set_id) + { + std::vector tags; + const int n_edges = self.n_entities(1); + tags.reserve(static_cast(n_edges)); + for (int e = 0; e < n_edges; ++e) + tags.push_back(static_cast(self.get_edge_root_tag(level_set_id, e))); + return as_nbarray(std::move(tags)); + }, + nb::arg("level_set_id")) + .def( + "cell_cert_tags", + [](const AdaptCellT& self, int level_set_id) + { + std::vector tags; + const int n_cells = self.n_entities(self.tdim); + tags.reserve(static_cast(n_cells)); + for (int c = 0; c < n_cells; ++c) + tags.push_back(static_cast(self.get_cell_cert_tag(level_set_id, c))); + return as_nbarray(std::move(tags)); + }, + nb::arg("level_set_id")) + .def( + "edge_green_split_params", + [](const AdaptCellT& self, int level_set_id) + { + const int n_edges = self.n_entities(1); + std::vector params(static_cast(n_edges), T(0)); + for (int e = 0; e < n_edges; ++e) + { + const auto idx = static_cast(level_set_id * n_edges + e); + if (idx < self.edge_green_split_param.size()) + params[static_cast(e)] = self.edge_green_split_param[idx]; + } + return as_nbarray(std::move(params)); + }, + nb::arg("level_set_id")) + .def( + "edge_green_split_mask", + [](const AdaptCellT& self, int level_set_id) + { + const int n_edges = self.n_entities(1); + std::vector mask(static_cast(n_edges), 0); + for (int e = 0; e < n_edges; ++e) + { + const auto idx = static_cast(level_set_id * n_edges + e); + if (idx < self.edge_green_split_has_value.size()) + mask[static_cast(e)] = self.edge_green_split_has_value[idx]; + } + return as_nbarray(std::move(mask)); + }, + nb::arg("level_set_id")); + + const std::string lsc_name = "LevelSetCell_" + suffix; + nb::class_(m, lsc_name.c_str(), "Cell-local Bernstein level set") + .def(nb::init<>()) + .def_prop_ro("cell_id", [](const LevelSetCellT& self) { return self.cell_id; }) + .def_prop_ro("bernstein_order", [](const LevelSetCellT& self) { return self.bernstein_order; }) + .def_prop_ro("cell_type", [](const LevelSetCellT& self) { return self.cell_type; }) + .def_prop_ro( + "bernstein_coeffs", + [](const LevelSetCellT& self) + { + return nb::ndarray( + self.bernstein_coeffs.data(), + {self.bernstein_coeffs.size()}, + nb::cast(self, nb::rv_policy::reference)); + }, + nb::rv_policy::reference_internal); + + m.def( + "make_adapt_cell", + [](const MeshViewT& mesh, int cell_id) + { + nb::gil_scoped_release release; + return cutcells::make_adapt_cell(mesh, cell_id); + }, + nb::arg("mesh"), + nb::arg("cell_id")); + + m.def( + "build_edges", + [](AdaptCellT& adapt_cell) + { + nb::gil_scoped_release release; + cutcells::build_edges(adapt_cell); + }, + nb::arg("adapt_cell")); + + m.def( + "build_faces", + [](AdaptCellT& adapt_cell) + { + nb::gil_scoped_release release; + cutcells::build_faces(adapt_cell); + }, + nb::arg("adapt_cell")); + + m.def( + "make_cell_level_set", + [](const LevelSetT& ls, int cell_id) + { + nb::gil_scoped_release release; + return cutcells::make_cell_level_set(ls, cell_id); + }, + nb::arg("level_set"), + nb::arg("cell_id")); + + m.def( + "evaluate_bernstein", + [](cell::type cell_type, int degree, const ndarray1& coeffs, const ndarray1& xi) + { + return cutcells::bernstein::evaluate( + cell_type, + degree, + std::span(coeffs.data(), static_cast(coeffs.size())), + std::span(xi.data(), static_cast(xi.size()))); + }, + nb::arg("cell_type"), + nb::arg("degree"), + nb::arg("coeffs"), + nb::arg("xi")); + + m.def( + "extract_parent_edge_bernstein", + [](cell::type parent_cell_type, int degree, + const ndarray1& parent_coeffs, int parent_local_edge_id) + { + std::vector out; + nb::gil_scoped_release release; + cutcells::extract_parent_edge_bernstein( + parent_cell_type, + degree, + std::span(parent_coeffs.data(), static_cast(parent_coeffs.size())), + parent_local_edge_id, + out); + return out; + }, + nb::arg("parent_cell_type"), + nb::arg("degree"), + nb::arg("parent_coeffs"), + nb::arg("parent_local_edge_id")); + + m.def( + "restrict_edge_bernstein_exact", + [](cell::type parent_cell_type, int degree, + const ndarray1& parent_coeffs, + const ndarray1& xi_a, + const ndarray1& xi_b) + { + std::vector out; + nb::gil_scoped_release release; + cutcells::restrict_edge_bernstein_exact( + parent_cell_type, + degree, + std::span(parent_coeffs.data(), static_cast(parent_coeffs.size())), + std::span(xi_a.data(), static_cast(xi_a.size())), + std::span(xi_b.data(), static_cast(xi_b.size())), + out); + return out; + }, + nb::arg("parent_cell_type"), + nb::arg("degree"), + nb::arg("parent_coeffs"), + nb::arg("xi_a"), + nb::arg("xi_b")); + + m.def( + "classify_edge_roots", + [](const ndarray1& edge_coeffs, + T zero_tol, + T sign_tol, + int max_depth) + { + T split_t = T(0); + bool has_split = false; + EdgeRootTag tag = cutcells::classify_edge_roots( + std::span(edge_coeffs.data(), static_cast(edge_coeffs.size())), + zero_tol, + sign_tol, + max_depth, + split_t, + has_split); + return nb::make_tuple(tag, has_split ? nb::cast(split_t) : nb::none()); + }, + nb::arg("edge_coeffs"), + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12), + nb::arg("max_depth") = 20); + + m.def( + "classify_new_edges", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + T zero_tol, T sign_tol, int max_depth) + { + nb::gil_scoped_release release; + cutcells::classify_new_edges(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol, max_depth); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12), + nb::arg("max_depth") = 20); + + m.def( + "fill_all_vertex_signs_from_level_set", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + T zero_tol) + { + nb::gil_scoped_release release; + cutcells::fill_all_vertex_signs_from_level_set( + adapt_cell, ls_cell, level_set_id, zero_tol); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("zero_tol") = T(1e-12)); + + m.def( + "classify_leaf_cells", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + T zero_tol, T sign_tol) + { + nb::gil_scoped_release release; + cutcells::classify_leaf_cells(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12)); + + m.def( + "classify_leaf_faces", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + T zero_tol, T sign_tol) + { + nb::gil_scoped_release release; + cutcells::classify_leaf_faces(adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12)); + + m.def( + "process_ready_to_cut_cells", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + T zero_tol, T sign_tol, int edge_max_depth, bool triangulate_cut_parts) + { + nb::gil_scoped_release release; + cutcells::process_ready_to_cut_cells( + adapt_cell, ls_cell, level_set_id, + zero_tol, sign_tol, edge_max_depth, triangulate_cut_parts); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12), + nb::arg("edge_max_depth") = 20, + nb::arg("triangulate_cut_parts") = false); + + m.def( + "refine_ready_cell_on_largest_midpoint_value", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, + int level_set_id, int cell_id) + { + nb::gil_scoped_release release; + return cutcells::refine_ready_cell_on_largest_midpoint_value( + adapt_cell, ls_cell, level_set_id, cell_id); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("cell_id")); + + m.def( + "refine_green_on_multiple_root_edges", + [](AdaptCellT& adapt_cell, int level_set_id) + { + nb::gil_scoped_release release; + return cutcells::refine_green_on_multiple_root_edges(adapt_cell, level_set_id); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_id")); + + m.def( + "refine_red_on_ambiguous_cells", + [](AdaptCellT& adapt_cell, int level_set_id) + { + nb::gil_scoped_release release; + return cutcells::refine_red_on_ambiguous_cells(adapt_cell, level_set_id); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_id")); + + m.def( + "certify_refine_and_process_ready_cells", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + int max_iterations, T zero_tol, T sign_tol, int edge_max_depth, + bool triangulate_cut_parts) + { + nb::gil_scoped_release release; + cutcells::certify_refine_and_process_ready_cells( + adapt_cell, ls_cell, level_set_id, + max_iterations, zero_tol, sign_tol, edge_max_depth, + triangulate_cut_parts); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("max_iterations") = 8, + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12), + nb::arg("edge_max_depth") = 20, + nb::arg("triangulate_cut_parts") = false); + + m.def( + "certify_and_refine", + [](AdaptCellT& adapt_cell, const LevelSetCellT& ls_cell, int level_set_id, + int max_iterations, T zero_tol, T sign_tol, int edge_max_depth) + { + nb::gil_scoped_release release; + cutcells::certify_and_refine(adapt_cell, ls_cell, level_set_id, + max_iterations, zero_tol, sign_tol, + edge_max_depth); + }, + nb::arg("adapt_cell"), + nb::arg("level_set_cell"), + nb::arg("level_set_id"), + nb::arg("max_iterations") = 8, + nb::arg("zero_tol") = T(1e-12), + nb::arg("sign_tol") = T(1e-12), + nb::arg("edge_max_depth") = 20); + + if constexpr (std::is_same_v) + { + m.attr("AdaptCell") = m.attr(adapt_name.c_str()); + m.attr("LevelSetCell") = m.attr(lsc_name.c_str()); + } } } // namespace @@ -330,6 +2689,81 @@ NB_MODULE(_cutcellscpp, m) .value("prism", cell::type::prism) .value("pyramid", cell::type::pyramid); + nb::enum_(m, "EdgeRootTag") + .value("not_classified", cutcells::EdgeRootTag::not_classified) + .value("no_root", cutcells::EdgeRootTag::no_root) + .value("one_root", cutcells::EdgeRootTag::one_root) + .value("multiple_roots", cutcells::EdgeRootTag::multiple_roots) + .value("zero", cutcells::EdgeRootTag::zero); + + nb::enum_(m, "CellCertTag") + .value("not_classified", cutcells::CellCertTag::not_classified) + .value("positive", cutcells::CellCertTag::positive) + .value("negative", cutcells::CellCertTag::negative) + .value("cut", cutcells::CellCertTag::cut) + .value("zero", cutcells::CellCertTag::zero) + .value("ambiguous", cutcells::CellCertTag::ambiguous) + .value("ready_to_cut", cutcells::CellCertTag::ready_to_cut); + + nb::enum_(m, "FaceCertTag") + .value("not_classified", cutcells::FaceCertTag::not_classified) + .value("positive", cutcells::FaceCertTag::positive) + .value("negative", cutcells::FaceCertTag::negative) + .value("cut", cutcells::FaceCertTag::cut) + .value("zero", cutcells::FaceCertTag::zero) + .value("ambiguous", cutcells::FaceCertTag::ambiguous); + declare_float(m, "float32"); declare_float(m, "float64"); + + declare_meshview_and_levelset(m, "float32"); + declare_meshview_and_levelset(m, "float64"); + declare_certification(m, "float32"); + declare_certification(m, "float64"); + + declare_ho_cut(m, "float32"); + declare_ho_cut(m, "float64"); + + m.def("csr_to_vtk_cells", + [](const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offsets) + { + return as_nbarray(csr_to_vtk_cells_impl( + std::span(connectivity.data(), connectivity.size()), + std::span(offsets.data(), offsets.size()))); + }, + nb::arg("connectivity"), + nb::arg("offsets"), + "Pack CSR connectivity/offsets to VTK cells layout [n0, v0..., n1, v1..., ...]."); + + m.def("write_vtk", + [](std::string filename, + const nb::ndarray, nb::c_contig>& vertex_coordinates, + const nb::ndarray, nb::c_contig>& connectivity, + const nb::ndarray, nb::c_contig>& offsets, + const nb::ndarray, nb::c_contig>& element_types, + int gdim) + { + std::vector types; + types.reserve(element_types.size()); + for (std::size_t i = 0; i < element_types.size(); ++i) + types.push_back(static_cast(element_types.data()[i])); + + nb::gil_scoped_release release; + io::write_vtk( + std::move(filename), + std::span(vertex_coordinates.data(), vertex_coordinates.size()), + std::span(connectivity.data(), connectivity.size()), + std::span(offsets.data(), offsets.size()), + std::span(types.data(), types.size()), + gdim); + }, + nb::arg("filename"), + nb::arg("vertex_coordinates"), + nb::arg("connectivity"), + nb::arg("offsets"), + nb::arg("element_types"), + nb::arg("gdim"), + "Write an unstructured VTK XML file using the existing C++ writer.\n" + "vertex_coordinates is a flat array of length num_points * gdim."); } diff --git a/python/demo/cut_2nd_order_tetrahedron/cut_tetrahedron.py b/python/demo/cut_2nd_order_tetrahedron/cut_tetrahedron.py index 068fe07..effcca4 100644 --- a/python/demo/cut_2nd_order_tetrahedron/cut_tetrahedron.py +++ b/python/demo/cut_2nd_order_tetrahedron/cut_tetrahedron.py @@ -2,6 +2,7 @@ import numpy as np import pyvista as pv + subdivision = np.array([[2, 3], [1, 3], [1, 2], [0, 3], [0, 2], [0, 1]]) # e01 : 4 , e12: 5, e02: 6, e03: 7, e13: 8, e23: 9 @@ -64,14 +65,14 @@ def level_set(x, c, r): # pv.start_xvfb() grid_int = pv.UnstructuredGrid( - cut_cell_int.connectivity, - cut_cell_int.types, - cut_cell_int.vertex_coords, + cut_cell_int.cells, + cut_cell_int.vtk_types, + np.asarray(cut_cell_int.vertex_coords), ) grid_ext = pv.UnstructuredGrid( - cut_cell_ext.connectivity, - cut_cell_ext.types, - cut_cell_ext.vertex_coords, + cut_cell_ext.cells, + cut_cell_ext.vtk_types, + np.asarray(cut_cell_ext.vertex_coords), ) plotter = pv.Plotter() # off_screen=True diff --git a/python/demo/cut_2nd_order_triangle/cut_triangle.py b/python/demo/cut_2nd_order_triangle/cut_triangle.py index 95be98c..c7e42bf 100644 --- a/python/demo/cut_2nd_order_triangle/cut_triangle.py +++ b/python/demo/cut_2nd_order_triangle/cut_triangle.py @@ -39,8 +39,16 @@ def cut_cell_type_to_pyvista(cut_cell_type): if pts_ext.shape[1] == 2: pts_ext = np.c_[pts_ext, np.zeros((pts_ext.shape[0],), dtype=pts_ext.dtype)] -grid_int = pv.UnstructuredGrid(cut_cell_int.connectivity, cut_cell_int.types, pts_int) -grid_ext = pv.UnstructuredGrid(cut_cell_ext.connectivity, cut_cell_ext.types, pts_ext) +grid_int = pv.UnstructuredGrid( + cut_cell_int.cells, + cut_cell_int.vtk_types, + pts_int, +) +grid_ext = pv.UnstructuredGrid( + cut_cell_ext.cells, + cut_cell_ext.vtk_types, + pts_ext, +) plotter = pv.Plotter() # off_screen=True plotter.set_background("white", top="white") diff --git a/python/demo/cut_hexahedron/cut_hexahedron_cases_subplots.py b/python/demo/cut_hexahedron/cut_hexahedron_cases_subplots.py index 95afb9d..e28046e 100644 --- a/python/demo/cut_hexahedron/cut_hexahedron_cases_subplots.py +++ b/python/demo/cut_hexahedron/cut_hexahedron_cases_subplots.py @@ -121,7 +121,9 @@ def render_batch(batch_index: int) -> None: ) inside_points = np.asarray(inside_cell.vertex_coords) inside_grid = pv.UnstructuredGrid( - inside_cell.connectivity, inside_cell.types, inside_points + inside_cell.cells, + inside_cell.vtk_types, + inside_points, ) # Interface surface (phi=0) @@ -136,7 +138,9 @@ def render_batch(batch_index: int) -> None: points = np.asarray(cut_cell.vertex_coords) grid = pv.UnstructuredGrid( - cut_cell.connectivity, cut_cell.types, points + cut_cell.cells, + cut_cell.vtk_types, + points, ) edge_mask = np.array([on_unit_cube_edge(p) for p in points], dtype=bool) diff --git a/python/demo/cut_hexahedron/cut_hexahedron_gyroid_thick.py b/python/demo/cut_hexahedron/cut_hexahedron_gyroid_thick.py index a97183c..1c9f699 100644 --- a/python/demo/cut_hexahedron/cut_hexahedron_gyroid_thick.py +++ b/python/demo/cut_hexahedron/cut_hexahedron_gyroid_thick.py @@ -36,7 +36,11 @@ def create_structured_hex_mesh( def create_cut_mesh( grid: pv.UnstructuredGrid, k: float, thickness: float, triangulate: bool ) -> pv.UnstructuredGrid: - points = grid.points + points = np.asarray(grid.points, dtype=np.float64) + points_flat = points.reshape(-1) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offset = np.asarray(grid.offset, dtype=np.int32) + celltypes = np.asarray(grid.celltypes, dtype=np.int32) ls_values = np.zeros(len(points), dtype=float) for j, point in enumerate(points): @@ -44,27 +48,27 @@ def create_cut_mesh( cut_mesh = cutcells.cut_vtk_mesh( ls_values, - points, - grid.cell_connectivity, - grid.offset, - grid.celltypes, + points_flat, + connectivity, + offset, + celltypes, "phi<0", triangulate, ) inside_cells = cutcells.locate_cells( ls_values, - points, - grid.cell_connectivity, - grid.offset, - grid.celltypes, + points_flat, + connectivity, + offset, + celltypes, "phi<0", ) pv_cut = pv.UnstructuredGrid( cut_mesh.cells, - cut_mesh.types, - cut_mesh.vertex_coords, + cut_mesh.vtk_types, + np.asarray(cut_mesh.vertex_coords), ) extract = grid.extract_cells(inside_cells) @@ -151,24 +155,27 @@ def main() -> None: # Optionally write interface, inside and outside VTK files if args.write_prefix: - pts = grid.points + pts = np.asarray(grid.points, dtype=np.float64) + pts_flat = pts.reshape(-1) ls_values = np.zeros(len(pts), dtype=float) for j, point in enumerate(pts): ls_values[j] = level_set_thick_gyroid( point, k=k, thickness=float(args.thickness) ) - conn = grid.cell_connectivity - off = grid.offset - ctypes = grid.celltypes + conn = np.asarray(grid.cell_connectivity, dtype=np.int32) + off = np.asarray(grid.offset, dtype=np.int32) + ctypes = np.asarray(grid.celltypes, dtype=np.int32) # Interface (phi=0) try: iface = cutcells.cut_vtk_mesh( - ls_values, pts, conn, off, ctypes, "phi=0", args.triangulate + ls_values, pts_flat, conn, off, ctypes, "phi=0", args.triangulate ) iface_pv = pv.UnstructuredGrid( - iface.cells, iface.types, iface.vertex_coords + iface.cells, + iface.vtk_types, + np.asarray(iface.vertex_coords), ) iface_file = f"{args.write_prefix}_interface.vtu" iface_pv.save(iface_file) @@ -179,10 +186,12 @@ def main() -> None: # Inside (phi<0) try: inside = cutcells.cut_vtk_mesh( - ls_values, pts, conn, off, ctypes, "phi<0", args.triangulate + ls_values, pts_flat, conn, off, ctypes, "phi<0", args.triangulate ) inside_pv = pv.UnstructuredGrid( - inside.cells, inside.types, inside.vertex_coords + inside.cells, + inside.vtk_types, + np.asarray(inside.vertex_coords), ) inside_file = f"{args.write_prefix}_inside.vtu" inside_pv.save(inside_file) @@ -193,10 +202,12 @@ def main() -> None: # Outside (phi>0) try: outside = cutcells.cut_vtk_mesh( - ls_values, pts, conn, off, ctypes, "phi>0", args.triangulate + ls_values, pts_flat, conn, off, ctypes, "phi>0", args.triangulate ) outside_pv = pv.UnstructuredGrid( - outside.cells, outside.types, outside.vertex_coords + outside.cells, + outside.vtk_types, + np.asarray(outside.vertex_coords), ) outside_file = f"{args.write_prefix}_outside.vtu" outside_pv.save(outside_file) diff --git a/python/demo/cut_hexahedron/cut_hexahedron_n0.py b/python/demo/cut_hexahedron/cut_hexahedron_n0.py index 4574519..22ecca6 100644 --- a/python/demo/cut_hexahedron/cut_hexahedron_n0.py +++ b/python/demo/cut_hexahedron/cut_hexahedron_n0.py @@ -73,7 +73,11 @@ def main() -> None: ) points = np.asarray(cut_cell.vertex_coords) - grid = pv.UnstructuredGrid(cut_cell.connectivity, cut_cell.types, points) + grid = pv.UnstructuredGrid( + cut_cell.cells, + cut_cell.vtk_types, + points, + ) edge_mask = np.array([on_unit_cube_edge(p) for p in points], dtype=bool) interior_points = points[~edge_mask] diff --git a/python/demo/cut_prism/cut_prism_n0.py b/python/demo/cut_prism/cut_prism_n0.py index 88c04b8..716e7c7 100644 --- a/python/demo/cut_prism/cut_prism_n0.py +++ b/python/demo/cut_prism/cut_prism_n0.py @@ -71,7 +71,11 @@ def main() -> None: ) points = np.asarray(cut_cell.vertex_coords) - grid = pv.UnstructuredGrid(cut_cell.connectivity, cut_cell.types, points) + grid = pv.UnstructuredGrid( + cut_cell.cells, + cut_cell.vtk_types, + points, + ) interior_mask = np.array( [is_strictly_inside_unit_prism(p) for p in points], dtype=bool diff --git a/python/demo/cut_pyramid/cut_pyramid_n0.py b/python/demo/cut_pyramid/cut_pyramid_n0.py index 42963e2..62f2a8d 100644 --- a/python/demo/cut_pyramid/cut_pyramid_n0.py +++ b/python/demo/cut_pyramid/cut_pyramid_n0.py @@ -77,7 +77,11 @@ def main() -> None: ) points = np.asarray(cut_cell.vertex_coords) - grid = pv.UnstructuredGrid(cut_cell.connectivity, cut_cell.types, points) + grid = pv.UnstructuredGrid( + cut_cell.cells, + cut_cell.vtk_types, + points, + ) interior_mask = np.array( [is_strictly_inside_unit_pyramid(p) for p in points], dtype=bool diff --git a/python/demo/cut_quadrilateral/plot_quadrilateral_cases.py b/python/demo/cut_quadrilateral/plot_quadrilateral_cases.py index c0d7b94..254164a 100644 --- a/python/demo/cut_quadrilateral/plot_quadrilateral_cases.py +++ b/python/demo/cut_quadrilateral/plot_quadrilateral_cases.py @@ -131,8 +131,8 @@ def _plot_runtime_cutcell( ): coords = np.asarray(cut_cell.vertex_coords) pts = [(float(p[0]), float(p[1])) for p in coords] - connectivity = list(cut_cell.connectivity) - types = list(cut_cell.types) + connectivity = list(cut_cell.cells) + types = list(cut_cell.vtk_types) for cell_type, verts in zip(types, _iter_connectivity(connectivity)): poly = [pts[v] for v in verts] diff --git a/python/demo/cut_tetrahedron/cut_tetrahedron.py b/python/demo/cut_tetrahedron/cut_tetrahedron.py index 7c138dd..d7f67ae 100644 --- a/python/demo/cut_tetrahedron/cut_tetrahedron.py +++ b/python/demo/cut_tetrahedron/cut_tetrahedron.py @@ -32,14 +32,14 @@ pv.start_xvfb() grid_int = pv.UnstructuredGrid( - cut_cell_int.connectivity, - cut_cell_int.types, - cut_cell_int.vertex_coords, + cut_cell_int.cells, + cut_cell_int.vtk_types, + np.asarray(cut_cell_int.vertex_coords), ) grid_ext = pv.UnstructuredGrid( - cut_cell_ext.connectivity, - cut_cell_ext.types, - cut_cell_ext.vertex_coords, + cut_cell_ext.cells, + cut_cell_ext.vtk_types, + np.asarray(cut_cell_ext.vertex_coords), ) split_cells_int = grid_int.explode() diff --git a/python/demo/cut_triangle/cut_triangle.py b/python/demo/cut_triangle/cut_triangle.py index 9cc41c9..ae6e347 100644 --- a/python/demo/cut_triangle/cut_triangle.py +++ b/python/demo/cut_triangle/cut_triangle.py @@ -46,8 +46,16 @@ def cut_cell_type_to_pyvista(cut_cell_type): if pts_ext.shape[1] == 2: pts_ext = np.c_[pts_ext, np.zeros((pts_ext.shape[0],), dtype=pts_ext.dtype)] -grid_int = pv.UnstructuredGrid(cut_cell_int.connectivity, cut_cell_int.types, pts_int) -grid_ext = pv.UnstructuredGrid(cut_cell_ext.connectivity, cut_cell_ext.types, pts_ext) +grid_int = pv.UnstructuredGrid( + cut_cell_int.cells, + cut_cell_int.vtk_types, + pts_int, +) +grid_ext = pv.UnstructuredGrid( + cut_cell_ext.cells, + cut_cell_ext.vtk_types, + pts_ext, +) plotter = pv.Plotter(off_screen=False) plotter.set_background("white", top="white") diff --git a/python/demo/cut_vtk_mesh/cut_hex_mesh_3D.py b/python/demo/cut_vtk_mesh/cut_hex_mesh_3D.py index 7ec41c5..0aded03 100644 --- a/python/demo/cut_vtk_mesh/cut_hex_mesh_3D.py +++ b/python/demo/cut_vtk_mesh/cut_hex_mesh_3D.py @@ -88,10 +88,14 @@ def main(): ) ls_values = np.array([level_set(p) for p in points], dtype=float) + points_flat = np.asarray(points, dtype=np.float64).reshape(-1) + connectivity = np.asarray(connectivity, dtype=np.int32) + offset = np.asarray(offset, dtype=np.int32) + celltypes = np.asarray(celltypes, dtype=np.int32) cut_mesh = cutcells.cut_vtk_mesh( ls_values, - points, + points_flat, connectivity, offset, celltypes, @@ -107,8 +111,8 @@ def main(): pv_in = pv.UnstructuredGrid(cells, celltypes, points) pv_cut = pv.UnstructuredGrid( cut_mesh.cells, - cut_mesh.types, - cut_mesh.vertex_coords, + cut_mesh.vtk_types, + np.asarray(cut_mesh.vertex_coords), ) pl = pv.Plotter() diff --git a/python/demo/cut_vtk_mesh/cut_hybrid_flower_2D.py b/python/demo/cut_vtk_mesh/cut_hybrid_flower_2D.py index 2079263..71ab1bd 100644 --- a/python/demo/cut_vtk_mesh/cut_hybrid_flower_2D.py +++ b/python/demo/cut_vtk_mesh/cut_hybrid_flower_2D.py @@ -67,7 +67,11 @@ def level_set(xs, R0=1.0, a=0.25, k=6, center=(0.0, 0.0), sdf_like=True, eps=1e- def create_cut_mesh(grid): - points = grid.points + points = np.asarray(grid.points, dtype=np.float64) + points_flat = points.reshape(-1) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offset = np.asarray(grid.offset, dtype=np.int32) + celltypes = np.asarray(grid.celltypes, dtype=np.int32) ls_values = np.zeros(len(points)) j = 0 for point in points: @@ -75,14 +79,16 @@ def create_cut_mesh(grid): j = j + 1 cut_mesh = cutcells.cut_vtk_mesh( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) inside_cells = cutcells.locate_cells( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) pv_cut = pv.UnstructuredGrid( - cut_mesh.cells, cut_mesh.types, np.asarray(cut_mesh.vertex_coords) + cut_mesh.cells, + cut_mesh.vtk_types, + np.asarray(cut_mesh.vertex_coords), ) extract = grid.extract_cells(inside_cells) diff --git a/python/demo/cut_vtk_mesh/cut_mesh_2D.py b/python/demo/cut_vtk_mesh/cut_mesh_2D.py index 1a9411d..48c4c10 100644 --- a/python/demo/cut_vtk_mesh/cut_mesh_2D.py +++ b/python/demo/cut_vtk_mesh/cut_mesh_2D.py @@ -28,7 +28,11 @@ def create_rectangle_mesh(x0, y0, x1, y1, Nx, Ny): def create_cut_mesh(grid): - points = grid.points + points = np.asarray(grid.points, dtype=np.float64) + points_flat = points.reshape(-1) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offset = np.asarray(grid.offset, dtype=np.int32) + celltypes = np.asarray(grid.celltypes, dtype=np.int32) ls_values = np.zeros(len(points)) j = 0 for point in points: @@ -36,14 +40,16 @@ def create_cut_mesh(grid): j = j + 1 cut_mesh = cutcells.cut_vtk_mesh( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) inside_cells = cutcells.locate_cells( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) pv_cut = pv.UnstructuredGrid( - cut_mesh.cells, cut_mesh.types, np.asarray(cut_mesh.vertex_coords) + cut_mesh.cells, + cut_mesh.vtk_types, + np.asarray(cut_mesh.vertex_coords), ) extract = grid.extract_cells(inside_cells) diff --git a/python/demo/cut_vtk_mesh/cut_mesh_3D.py b/python/demo/cut_vtk_mesh/cut_mesh_3D.py index 7e70f2e..3cd6e62 100644 --- a/python/demo/cut_vtk_mesh/cut_mesh_3D.py +++ b/python/demo/cut_vtk_mesh/cut_mesh_3D.py @@ -71,7 +71,11 @@ def create_box_mesh(x0, y0, z0, x1, y1, z1, Nx, Ny, Nz): def create_cut_mesh(grid): - points = grid.points + points = np.asarray(grid.points, dtype=np.float64) + points_flat = points.reshape(-1) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offset = np.asarray(grid.offset, dtype=np.int32) + celltypes = np.asarray(grid.celltypes, dtype=np.int32) ls_values = np.zeros(len(points)) j = 0 for point in points: @@ -79,16 +83,16 @@ def create_cut_mesh(grid): j = j + 1 cut_mesh = cutcells.cut_vtk_mesh( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) inside_cells = cutcells.locate_cells( - ls_values, points, grid.cell_connectivity, grid.offset, grid.celltypes, "phi<0" + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" ) pv_cut = pv.UnstructuredGrid( cut_mesh.cells, - cut_mesh.types, - cut_mesh.vertex_coords, + cut_mesh.vtk_types, + np.asarray(cut_mesh.vertex_coords), ) extract = grid.extract_cells(inside_cells) diff --git a/python/demo/cut_vtk_mesh/cut_popcorn_hex_mesh_3D.py b/python/demo/cut_vtk_mesh/cut_popcorn_hex_mesh_3D.py index 3937e72..8f1cc3b 100644 --- a/python/demo/cut_vtk_mesh/cut_popcorn_hex_mesh_3D.py +++ b/python/demo/cut_vtk_mesh/cut_popcorn_hex_mesh_3D.py @@ -70,7 +70,11 @@ def create_hex_box_mesh(x0, y0, z0, x1, y1, z1, nx, ny, nz) -> pv.UnstructuredGr def create_cut_mesh( grid: pv.UnstructuredGrid, triangulate: bool ) -> pv.UnstructuredGrid: - points = grid.points + points = np.asarray(grid.points, dtype=np.float64) + points_flat = points.reshape(-1) + connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) + offset = np.asarray(grid.offset, dtype=np.int32) + celltypes = np.asarray(grid.celltypes, dtype=np.int32) ls_values = np.zeros(len(points), dtype=float) for j, point in enumerate(points): @@ -78,26 +82,26 @@ def create_cut_mesh( cut_mesh = cutcells.cut_vtk_mesh( ls_values, - points, - grid.cell_connectivity, - grid.offset, - grid.celltypes, + points_flat, + connectivity, + offset, + celltypes, "phi<0", triangulate, ) inside_cells = cutcells.locate_cells( ls_values, - points, - grid.cell_connectivity, - grid.offset, - grid.celltypes, + points_flat, + connectivity, + offset, + celltypes, "phi<0", ) pv_cut = pv.UnstructuredGrid( cut_mesh.cells, - cut_mesh.types, - cut_mesh.vertex_coords, + np.asarray(cut_mesh.types, dtype=np.uint8), + np.asarray(cut_mesh.vertex_coords), ) extract = grid.extract_cells(inside_cells) diff --git a/python/demo/cut_vtk_mesh/cut_quad_mesh_2D.py b/python/demo/cut_vtk_mesh/cut_quad_mesh_2D.py index 2d1bd5a..1b78f02 100644 --- a/python/demo/cut_vtk_mesh/cut_quad_mesh_2D.py +++ b/python/demo/cut_vtk_mesh/cut_quad_mesh_2D.py @@ -126,12 +126,22 @@ def main() -> None: # level set at points ls = np.array([level_set_popcorn_2d(p) for p in points], dtype=float) + points_flat = points.reshape(-1) + conn = np.asarray(conn, dtype=np.int32) + off = np.asarray(off, dtype=np.int32) + celltypes = np.asarray(celltypes, dtype=np.int32) # Cut mesh and also extract all inside (phi<0) background cells for context cut_mesh = cutcells.cut_vtk_mesh( - ls, points, conn, off, celltypes, "phi<0", triangulate=bool(args.triangulate) + ls, + points_flat, + conn, + off, + celltypes, + "phi<0", + triangulate=bool(args.triangulate), ) - inside_cells = cutcells.locate_cells(ls, points, conn, off, celltypes, "phi<0") + inside_cells = cutcells.locate_cells(ls, points_flat, conn, off, celltypes, "phi<0") # Build a pyvista grid for visualization cells_with_counts = np.empty((len(off) * 5,), dtype=np.int32) @@ -145,7 +155,7 @@ def main() -> None: cut_points = np.asarray(cut_mesh.vertex_coords) cut_cells = np.asarray(cut_mesh.cells, dtype=np.int32) - cut_types = np.asarray(cut_mesh.types, dtype=np.int32) + cut_types = cut_mesh.vtk_types pv_cut = pv.UnstructuredGrid(cut_cells, cut_types, cut_points) extract = bg.extract_cells(inside_cells) merged = extract.merge(pv_cut) diff --git a/python/demo/demo_mesh_parts.py b/python/demo/demo_mesh_parts.py new file mode 100644 index 0000000..10fff2b --- /dev/null +++ b/python/demo/demo_mesh_parts.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 ONERA +# Authors: Susanne Claus +# This file is part of CutCells +# +# SPDX-License-Identifier: MIT +""" +Demo: MeshView + create_level_set + cut(mesh, ls) straight output bridge. + +This is the first user-facing step for the HO pipeline: +1. build a pyvista tetrahedral mesh, +2. convert it to a MeshView, +3. build one polynomial level set with create_level_set(...), +4. call cut(mesh, ls), +5. select HOMeshPart objects, +6. obtain straight hybrid visualization meshes and straight quadrature from HOMeshPart, +7. write VTU files. + +Curving is intentionally out of scope here. This demo only exercises the +straight decomposition / straight output bridge. +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import numpy as np + +try: + import pyvista as pv +except ImportError as exc: + raise SystemExit( + "pyvista is required for this demo. " + "Install with: python -m pip install pyvista\n" + f"Import error: {exc}" + ) + +import cutcells +from cutcells import box_tetrahedron_mesh, mesh_from_pyvista + + +CENTER = np.array([0.1, -0.05, 0.0], dtype=np.float64) +RADIUS = 0.55 + + +def phi_batch(X: np.ndarray) -> np.ndarray: + return np.sqrt( + (X[0] - CENTER[0]) ** 2 + + (X[1] - CENTER[1]) ** 2 + + (X[2] - CENTER[2]) ** 2 + ) - RADIUS + + +def cutmesh_to_pyvista(cut_mesh) -> pv.UnstructuredGrid: + return pv.UnstructuredGrid( + np.asarray(cut_mesh.cells, dtype=np.int64), + np.asarray(cut_mesh.vtk_types, dtype=np.uint8), + np.asarray(cut_mesh.vertex_coords, dtype=np.float64), + ) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Demo: use cut(mesh, ls) on a MeshView from a pyvista tetra mesh." + ) + parser.add_argument( + "--n", + type=int, + default=8, + help="Structured tetrahedral mesh resolution parameter.", + ) + parser.add_argument( + "--degree", + type=int, + default=2, + help="Polynomial degree for create_level_set(...).", + ) + parser.add_argument( + "--quadrature-order", + type=int, + default=4, + help="Quadrature order for HOMeshPart.quadrature(...).", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path("demo_meshview_ho_cut_output"), + help="Directory for VTU files.", + ) + parser.add_argument( + "--no-plot", + action="store_true", + help="Skip interactive pyvista plotting.", + ) + args = parser.parse_args() + + args.output_dir.mkdir(parents=True, exist_ok=True) + + print(f"Creating tetrahedral pyvista mesh with n={args.n} ...") + grid = box_tetrahedron_mesh( + -1.0, -1.0, -1.0, + 1.0, 1.0, 1.0, + args.n, args.n, args.n, + ) + mesh = mesh_from_pyvista(grid, tdim=3) + print(f" pyvista cells={grid.n_cells}, points={grid.n_points}") + print(f" MeshView gdim={mesh.gdim}, tdim={mesh.tdim}") + + print(f"Building polynomial level set with degree={args.degree} ...") + ls = cutcells.create_level_set(mesh, phi_batch, degree=args.degree, name="phi") + print(f" level-set dofs={ls.mesh_data.num_dofs()}") + + print("Running cut(mesh, ls) ...") + result = cutcells.cut(mesh, ls) + negative = result["phi < 0"] + interface = result["phi = 0"] + positive = result["phi > 0"] + + print(f" num_cut_cells={result.num_cut_cells}") + print(f" phi < 0 : cut={negative.num_cut_cells}, uncut={negative.num_uncut_cells}") + print(f" phi = 0 : cut={interface.num_cut_cells}, uncut={interface.num_uncut_cells}") + print(f" phi > 0 : cut={positive.num_cut_cells}, uncut={positive.num_uncut_cells}") + + print("Building straight hybrid visualization meshes from HOMeshPart ...") + inside_full = negative.visualization_mesh(mode="full") + inside_cut = negative.visualization_mesh(mode="cut_only") + interface_mesh = interface.visualization_mesh(mode="cut_only") + + print(f" inside full cells={len(np.asarray(inside_full.types))}") + print(f" inside cut-only cells={len(np.asarray(inside_cut.types))}") + print(f" interface cells={len(np.asarray(interface_mesh.types))}") + + print("Computing straight quadrature from HOMeshPart ...") + q_inside_full = negative.quadrature( + order=args.quadrature_order, + mode="full", + ) + q_inside_cut = negative.quadrature( + order=args.quadrature_order, + mode="cut_only", + ) + q_interface = interface.quadrature( + order=args.quadrature_order, + mode="cut_only", + ) + + print(f" inside full quadrature sum = {q_inside_full.weights.sum():.12g}") + print(f" inside cut-only quadrature sum= {q_inside_cut.weights.sum():.12g}") + print(f" interface quadrature sum = {q_interface.weights.sum():.12g}") + + inside_full_path = args.output_dir / "phi_negative_full_straight.vtu" + inside_cut_path = args.output_dir / "phi_negative_cut_only_straight.vtu" + interface_path = args.output_dir / "phi_interface_straight.vtu" + + negative.write_vtu(str(inside_full_path), mode="full") + negative.write_vtu(str(inside_cut_path), mode="cut_only") + interface.write_vtu(str(interface_path), mode="cut_only") + + print(f" wrote {inside_full_path}") + print(f" wrote {inside_cut_path}") + print(f" wrote {interface_path}") + + if args.no_plot: + return + + pv_inside_full = cutmesh_to_pyvista(inside_full) + pv_interface = cutmesh_to_pyvista(interface_mesh) + + plotter = pv.Plotter(shape=(1, 2), title="CutCells HO straight-output demo") + plotter.subplot(0, 0) + plotter.add_title("phi < 0, mode='full'") + plotter.add_mesh(grid, style="wireframe", color="lightgrey", opacity=0.25) + plotter.add_mesh(pv_inside_full, color="steelblue", opacity=0.75, show_edges=True) + + plotter.subplot(0, 1) + plotter.add_title("phi = 0") + plotter.add_mesh(grid, style="wireframe", color="lightgrey", opacity=0.15) + plotter.add_mesh(pv_interface, color="crimson", show_edges=True, line_width=2) + + plotter.link_views() + plotter.show() + + +if __name__ == "__main__": + main() diff --git a/python/demo/demo_two_level_sets.py b/python/demo/demo_two_level_sets.py new file mode 100644 index 0000000..043ec02 --- /dev/null +++ b/python/demo/demo_two_level_sets.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 ONERA +# Authors: Susanne Claus +# This file is part of CutCells +# +# SPDX-License-Identifier: MIT +""" +Demo: cut a tetrahedral mesh with two level sets. + +The example uses a sphere and a plane: + sphere(x) = |x - c| - r + plane(x) = n . x - offset + +It writes straight VTU output for several mesh-part selections, including +single-interface parts, a codimension-two intersection curve, and volume +regions selected by both level sets. +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import numpy as np + +import cutcells +from cutcells import box_tetrahedron_mesh, mesh_from_pyvista + + +DEFAULT_CENTER = np.array([0.1, -0.08, 0.05], dtype=np.float64) +DEFAULT_RADIUS = 0.58 +DEFAULT_PLANE_NORMAL = np.array([1.0, -0.35, 0.2], dtype=np.float64) +DEFAULT_PLANE_OFFSET = 0.0 +TRIANGULATE = True +OUTPUT_DIR = Path("demo_two_level_sets_output") + + +def sphere_phi(X: np.ndarray, center: np.ndarray, radius: float) -> np.ndarray: + return ( + np.sqrt( + (X[0] - center[0]) ** 2 + (X[1] - center[1]) ** 2 + (X[2] - center[2]) ** 2 + ) + - radius + ) + + +def plane_phi(X: np.ndarray, normal: np.ndarray, offset: float) -> np.ndarray: + return normal[0] * X[0] + normal[1] * X[1] + normal[2] * X[2] - offset + + +def write_part(part, path: Path, mode: str = "cut_only") -> None: + part.write_vtu(str(path), mode=mode) + print( + f" {path.name:<38} dim={part.dim}, " + f"cut={part.num_cut_cells}, uncut={part.num_uncut_cells}" + ) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Cut a tetrahedral mesh with a sphere and a plane level set." + ) + parser.add_argument( + "--n", + type=int, + default=7, + help="Number of grid points per coordinate direction.", + ) + args = parser.parse_args() + + if args.n < 2: + raise SystemExit("--n must be at least 2") + + center = DEFAULT_CENTER + normal = DEFAULT_PLANE_NORMAL.copy() + norm = float(np.linalg.norm(normal)) + if norm == 0.0: + raise RuntimeError("DEFAULT_PLANE_NORMAL must be nonzero") + normal /= norm + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + print(f"Building tetrahedral box mesh with n={args.n} ...") + grid = box_tetrahedron_mesh( + -1.0, + -1.0, + -1.0, + 1.0, + 1.0, + 1.0, + args.n, + args.n, + args.n, + ) + mesh = mesh_from_pyvista(grid, tdim=3) + print(f" cells={mesh.num_cells()}, points={mesh.num_nodes()}") + + background_path = OUTPUT_DIR / "background_mesh.vtu" + grid.save(background_path) + print(f" wrote {background_path}") + + print("Interpolating level sets ...") + sphere = cutcells.create_level_set( + mesh, + lambda X: sphere_phi(X, center, DEFAULT_RADIUS), + degree=1, + name="sphere", + ) + plane = cutcells.create_level_set( + mesh, + lambda X: plane_phi(X, normal, DEFAULT_PLANE_OFFSET), + degree=1, + name="plane", + ) + print(f" sphere dofs={sphere.mesh_data.num_dofs()}") + print(f" plane dofs={plane.mesh_data.num_dofs()}") + + print(f"Running cut(mesh, [sphere, plane], triangulate={TRIANGULATE}) ...") + result = cutcells.cut(mesh, [sphere, plane], triangulate=TRIANGULATE) + print(f" level sets={list(result.level_set_names)}") + print(f" num_cut_cells={result.num_cut_cells}") + + outputs = [ + ("sphere = 0 and plane < 0", "sphere_interface_plane_negative.vtu", "cut_only"), + ("sphere = 0 and plane > 0", "sphere_interface_plane_positive.vtu", "cut_only"), + ("plane = 0 and sphere < 0", "plane_disk_inside_sphere.vtu", "cut_only"), + ("sphere = 0 and plane = 0", "sphere_plane_intersection_curve.vtu", "cut_only"), + ("sphere < 0 and plane < 0", "inside_sphere_plane_negative.vtu", "cut_only"), + ("sphere < 0 and plane > 0", "inside_sphere_plane_positive.vtu", "cut_only"), + ("sphere < 0 or plane < 0", "inside_sphere_or_plane_negative.vtu", "cut_only"), + ] + + print("Writing selected mesh parts ...") + for expr, filename, mode in outputs: + part = result[expr] + write_part(part, OUTPUT_DIR / filename, mode=mode) + + +if __name__ == "__main__": + main() diff --git a/python/demo/quadrature/demo_quadrature_2D.py b/python/demo/quadrature/demo_quadrature_2D.py new file mode 100644 index 0000000..be6df2d --- /dev/null +++ b/python/demo/quadrature/demo_quadrature_2D.py @@ -0,0 +1,261 @@ +""" +Quadrature point visualisation — 2D (triangulated mesh, z = 0) +============================================================== +Creates a background triangular mesh in the square [-1, 1]^2 embedded in +the z = 0 plane and cuts it with a circular level-set phi(x) = ||x|| - r. + +Two quadrature integrals are computed and visualised: + + Area integral (phi < 0): ∫_{Ω_h} 1 dA ≈ π r² + · fully-inside triangles → standard reference Gauss rule + · cut triangles → cut-cell sub-triangulation rule + Perimeter integral (phi = 0): ∫_{Γ_h} 1 ds ≈ 2π r + · cut triangles → interface edge rule + +Both sets of quadrature points are displayed. Physical points are obtained +by mapping 2D reference coordinates through the affine triangle map to the +3D z = 0 plane. +""" + +import numpy as np +import pyvista as pv +import cutcells + +RADIUS = 0.7 +N = 22 # grid resolution (NxN before Delaunay) +ORDER = 2 # quadrature order + + +# --------------------------------------------------------------------------- +# Mesh +# --------------------------------------------------------------------------- +def circle_ls(pts: np.ndarray) -> np.ndarray: + return np.sqrt(pts[:, 0] ** 2 + pts[:, 1] ** 2) - RADIUS + + +def build_mesh(N: int) -> pv.UnstructuredGrid: + x = np.linspace(-1.0, 1.0, N) + y = np.linspace(-1.0, 1.0, N) + xx, yy, zz = np.meshgrid(x, y, [0.0]) + pts = np.c_[xx.ravel(), yy.ravel(), zz.ravel()] + return pv.UnstructuredGrid(pv.PolyData(pts).delaunay_2d()) + + +grid = build_mesh(N) +points = np.asarray(grid.points, dtype=np.float64) +ls_values = circle_ls(points) +points_flat = points.ravel() +connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) +offset = np.asarray(grid.offset, dtype=np.int32) +celltypes = np.asarray(grid.celltypes, dtype=np.int32) + +# --------------------------------------------------------------------------- +# Area quadrature (phi < 0 — inside + cut cells) +# --------------------------------------------------------------------------- +rules_area = cutcells.runtime_quadrature( + ls_values, + points_flat, + connectivity, + offset, + celltypes, + "phi<0", + True, + ORDER, +) +phys_area = cutcells.physical_points( + rules_area, points_flat, connectivity, offset, celltypes +).reshape(-1, 3) +w_area = np.asarray(rules_area.weights) +n_area = len(np.asarray(rules_area.parent_map)) + +# --------------------------------------------------------------------------- +# Perimeter quadrature (phi = 0 — interface) +# --------------------------------------------------------------------------- +rules_peri = cutcells.runtime_quadrature( + ls_values, + points_flat, + connectivity, + offset, + celltypes, + "phi=0", + True, + ORDER, +) +phys_peri = cutcells.physical_points( + rules_peri, points_flat, connectivity, offset, celltypes +).reshape(-1, 3) +w_peri = np.asarray(rules_peri.weights) +n_peri = len(np.asarray(rules_peri.parent_map)) + +exact_area = np.pi * RADIUS**2 +exact_peri = 2.0 * np.pi * RADIUS +print(f"Mesh : {grid.n_cells} cells ({N}×{N} Delaunay 2D)") +print( + f"Area : {n_area:4d} rules, {len(w_area):5d} qpts " + f"∫1 dA = {w_area.sum():.6f} (π r² = {exact_area:.6f} " + f"err = {abs(w_area.sum() - exact_area):.4f})" +) +print( + f"Peri : {n_peri:4d} rules, {len(w_peri):5d} qpts " + f"∫1 ds = {w_peri.sum():.6f} (2π r = {exact_peri:.6f} " + f"err = {abs(w_peri.sum() - exact_peri):.4f})" +) + +# --------------------------------------------------------------------------- +# Classify cells and split area q-pts by parent cell type +# --------------------------------------------------------------------------- +inside_ids = cutcells.locate_cells( + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" +) +inside_set = set(inside_ids.tolist()) + +n_cells = grid.n_cells +cell_min_ls = np.zeros(n_cells) +cell_max_ls = np.zeros(n_cells) +for ci in range(n_cells): + vids = connectivity[offset[ci] : offset[ci + 1]] + cell_min_ls[ci] = ls_values[vids].min() + cell_max_ls[ci] = ls_values[vids].max() +cut_ids = np.where((cell_min_ls < 0) & (cell_max_ls > 0))[0] + +# Split area quadrature points into inside-cell pts and cut-cell pts so the +# two populations can be shown in distinct colours (not via a weight colormap +# which confusingly mixes cell-type information with weight magnitude). +area_parent_map = np.asarray(rules_area.parent_map) +area_offset = np.asarray(rules_area.offset) +inside_mask = np.zeros(len(w_area), dtype=bool) +for rule_i, parent in enumerate(area_parent_map): + q0, q1 = int(area_offset[rule_i]), int(area_offset[rule_i + 1]) + if parent in inside_set: + inside_mask[q0:q1] = True + +phys_area_inside = phys_area[inside_mask] # full cells → reference Gauss rule +phys_area_cut = phys_area[~inside_mask] # cut cells → sub-triangulation rule + +# --------------------------------------------------------------------------- +# Build cut sub-meshes for visual verification +# --------------------------------------------------------------------------- +# Volume sub-triangulation of cut region (phi < 0 part of each cut triangle) +cut_vol = cutcells.cut_vtk_mesh( + ls_values, points_flat, connectivity, offset, celltypes, "phi<0" +) +pv_cut_vol = pv.UnstructuredGrid( + np.asarray(cut_vol.cells), + np.asarray(cut_vol.vtk_types), + np.asarray(cut_vol.vertex_coords), +) + +# Interface edges (phi = 0 — one interval per cut triangle) +cut_iface = cutcells.cut_vtk_mesh( + ls_values, points_flat, connectivity, offset, celltypes, "phi=0" +) +pv_cut_iface = pv.UnstructuredGrid( + np.asarray(cut_iface.cells), + np.asarray(cut_iface.vtk_types), + np.asarray(cut_iface.vertex_coords), +) + +# --------------------------------------------------------------------------- +# PyVista visualisation +# --------------------------------------------------------------------------- +POINT_SIZE = 8 # pixels, same for all point clouds → easy visual comparison + +pl = pv.Plotter(window_size=(1200, 900)) +pl.set_background("white") + +# Background wireframe +pl.add_mesh(grid, style="wireframe", color="lightgrey", line_width=0.5, opacity=0.6) + +# Inside cells (blue fill) +if len(inside_ids): + pl.add_mesh( + grid.extract_cells(inside_ids), + color="#80b3ff", + opacity=0.25, + show_edges=True, + edge_color="steelblue", + line_width=0.5, + label="inside cells", + ) + +# Cut sub-triangles of the volume part (phi<0) — dark green outline, transparent fill +pl.add_mesh( + pv_cut_vol, + color="#00aa44", + opacity=0.20, + show_edges=True, + edge_color="#007730", + line_width=1.2, + label="cut sub-tris (phi<0)", +) + +# Interface edges (phi=0) — thick magenta lines + their nodes +pl.add_mesh( + pv_cut_iface, + color="magenta", + line_width=3.0, + label="interface edges (phi=0)", +) +pl.add_points( + pv_cut_iface.points, + color="darkviolet", + point_size=10, + render_points_as_spheres=True, + label=f"interface nodes ({pv_cut_iface.n_points})", +) + +# Cut volume sub-triangle nodes +pl.add_points( + pv_cut_vol.points, + color="#007730", + point_size=7, + render_points_as_spheres=True, + label=f"cut vol nodes ({pv_cut_vol.n_points})", +) + +# Area q-pts in fully-inside cells (green) +if len(phys_area_inside): + pl.add_points( + phys_area_inside, + color="limegreen", + point_size=POINT_SIZE, + render_points_as_spheres=True, + label=f"area q-pts inside ({len(phys_area_inside)})", + ) + +# Area q-pts in cut cells (orange-red) +if len(phys_area_cut): + pl.add_points( + phys_area_cut, + color="orangered", + point_size=POINT_SIZE, + render_points_as_spheres=True, + label=f"area q-pts cut ({len(phys_area_cut)})", + ) + +# Perimeter q-pts (dark blue) +if len(phys_peri): + pl.add_points( + phys_peri, + color="navy", + point_size=POINT_SIZE, + render_points_as_spheres=True, + label=f"perimeter q-pts ({len(phys_peri)})", + ) + +# Circle outline +theta = np.linspace(0.0, 2.0 * np.pi, 512) +circle_pts = np.c_[RADIUS * np.cos(theta), RADIUS * np.sin(theta), np.zeros(512)] +pl.add_mesh( + pv.Spline(circle_pts, 512), color="black", line_width=2.0, label="level set φ = 0" +) + +pl.add_legend(bcolor="white", border=True, size=(0.42, 0.34)) +pl.camera_position = "xy" +pl.add_title( + f"Quadrature — 2D r={RADIUS} order={ORDER} " + f"∫dA≈{w_area.sum():.4f} [π r²={exact_area:.4f}] " + f"∫ds≈{w_peri.sum():.4f} [2πr={exact_peri:.4f}]", + font_size=9, +) +pl.show() diff --git a/python/demo/quadrature/demo_quadrature_3D.py b/python/demo/quadrature/demo_quadrature_3D.py new file mode 100644 index 0000000..6f40805 --- /dev/null +++ b/python/demo/quadrature/demo_quadrature_3D.py @@ -0,0 +1,196 @@ +""" +Quadrature point visualisation — 3D (tetrahedral mesh) +====================================================== +Creates a background tetrahedral mesh inside the box [-1, 1]^3 and cuts it +with a spherical level-set phi(x) = ||x|| - r. + +For every qualifying cell runtime_quadrature produces reference-space +quadrature rules; physical_points maps them to the physical domain. + +The plot shows: + - clipped background mesh (light grey wireframe) + - cut cells (red, semi-transparent) — one half of the sphere, clipped + - quadrature points (glyphs sized and coloured by weight) + - a translucent sphere surface at the interface +""" + +import numpy as np +import pyvista as pv +import cutcells + +RADIUS = 0.65 +N = 9 # grid resolution +ORDER = 3 # quadrature order +CUT_TYPE = "phi<0" + + +# --------------------------------------------------------------------------- +# Build mesh +# --------------------------------------------------------------------------- + + +def sphere_ls(pts: np.ndarray) -> np.ndarray: + """Signed distance to a sphere of radius RADIUS centred at the origin.""" + return np.sqrt((pts**2).sum(axis=1)) - RADIUS + + +def build_box_mesh(N: int) -> pv.UnstructuredGrid: + x = np.linspace(-1.0, 1.0, N) + y = np.linspace(-1.0, 1.0, N) + z = np.linspace(-1.0, 1.0, N) + xx, yy, zz = np.meshgrid(x, y, z) + pts = np.c_[xx.ravel(), yy.ravel(), zz.ravel()] + grid = pv.UnstructuredGrid(pv.PolyData(pts).delaunay_3d()) + return grid + + +grid = build_box_mesh(N) + +points = np.asarray(grid.points, dtype=np.float64) +ls_values = sphere_ls(points) +points_flat = points.ravel() +connectivity = np.asarray(grid.cell_connectivity, dtype=np.int32) +offset = np.asarray(grid.offset, dtype=np.int32) +celltypes = np.asarray(grid.celltypes, dtype=np.int32) + +# --------------------------------------------------------------------------- +# Quadrature via runtime_quadrature +# --------------------------------------------------------------------------- + +rules = cutcells.runtime_quadrature( + ls_values, points_flat, connectivity, offset, celltypes, CUT_TYPE, True, ORDER +) + +phys_pts = cutcells.physical_points( + rules, points_flat, connectivity, offset, celltypes +).reshape(-1, 3) + +weights = np.asarray(rules.weights) +parent_map = np.asarray(rules.parent_map) + +exact = (4.0 / 3.0) * np.pi * RADIUS**3 +print(f"Mesh: {grid.n_cells} cells (N = {N})") +print(f"Quadrature rules: {len(parent_map)} rules, {len(weights)} total points") +print(f"∫1 dV ≈ {weights.sum():.6f} (exact (4/3)π r³ = {exact:.6f})") + +# --------------------------------------------------------------------------- +# Classify cells for colouring +# --------------------------------------------------------------------------- + +inside_ids = cutcells.locate_cells( + ls_values, points_flat, connectivity, offset, celltypes, CUT_TYPE +) +# Cut cells: at least one vertex inside, at least one outside +n_cells = grid.n_cells +cell_min_ls = np.zeros(n_cells) +cell_max_ls = np.zeros(n_cells) +for ci in range(n_cells): + vids = connectivity[offset[ci] : offset[ci + 1]] + cell_min_ls[ci] = ls_values[vids].min() + cell_max_ls[ci] = ls_values[vids].max() +cut_parent_ids = np.where((cell_min_ls < 0) & (cell_max_ls > 0))[0].astype(np.int64) + +# --------------------------------------------------------------------------- +# PyVista visualisation +# --------------------------------------------------------------------------- + +# Clipping plane (y = 0: show x > 0 half for interior view) +clip_normal = (0.0, -1.0, 0.0) +clip_origin = (0.0, 0.0, 0.0) + +# Analytic sphere surface +sphere_surf = pv.Sphere(radius=RADIUS, theta_resolution=60, phi_resolution=60) + +# Quadrature points on the selected half +mask = phys_pts[:, 1] >= 0.0 +phys_half = phys_pts[mask] +weights_half = weights[mask] + +qpts_cloud = pv.PolyData(phys_half) +qpts_cloud["weight"] = weights_half + +glyph_geom = pv.Sphere(radius=1.0, theta_resolution=8, phi_resolution=8) +w_max = weights_half.max() if len(weights_half) else 1.0 +qpts_cloud["radius"] = 0.008 + 0.018 * (weights_half / w_max) + +pl = pv.Plotter(window_size=(1300, 1000)) +pl.set_background("white") + +# Clipped background mesh (wireframe) +grid_clipped = grid.clip(normal=clip_normal, origin=clip_origin, invert=True) +pl.add_mesh( + grid_clipped, style="wireframe", color="#cccccc", line_width=0.4, opacity=0.5 +) + +# Inside cells (clipped half) +if len(inside_ids) > 0: + inside_mesh = grid.extract_cells(inside_ids) + inside_clipped = inside_mesh.clip( + normal=clip_normal, origin=clip_origin, invert=True + ) + pl.add_mesh( + inside_clipped, + color="#80b3ff", + opacity=0.25, + show_edges=True, + edge_color="steelblue", + line_width=0.6, + label="inside cells", + ) + +# Cut cells (clipped half) +if len(cut_parent_ids) > 0: + cut_mesh = grid.extract_cells(cut_parent_ids) + cut_clipped = cut_mesh.clip(normal=clip_normal, origin=clip_origin, invert=True) + pl.add_mesh( + cut_clipped, + color="#ffaaaa", + opacity=0.40, + show_edges=True, + edge_color="#cc4444", + line_width=0.8, + label="cut cells", + ) + +# Sphere surface (translucent) +sphere_clipped = sphere_surf.clip(normal=clip_normal, origin=clip_origin, invert=True) +pl.add_mesh( + sphere_clipped, + color="lightyellow", + opacity=0.35, + show_edges=False, + label="level set", +) +# Outline ring at the clip plane +sphere_ring = sphere_surf.clip(normal=clip_normal, origin=clip_origin) +pl.add_mesh(sphere_ring, style="wireframe", color="gold", line_width=1.2, opacity=0.6) + +# Quadrature point glyphs (visible half only) +if len(phys_half) > 0: + glyphs = qpts_cloud.glyph(geom=glyph_geom, scale="radius", orient=False) + pl.add_mesh( + glyphs, + scalars="weight", + cmap="plasma", + show_scalar_bar=True, + scalar_bar_args={ + "title": "weight", + "n_labels": 4, + "position_x": 0.82, + "position_y": 0.25, + }, + label="quadrature pts", + ) + +pl.add_legend(bcolor="white", border=True, size=(0.25, 0.20), loc="upper right") +pl.add_title( + f"Quadrature points — 3D (r = {RADIUS}, order = {ORDER}, " + f"∫1 dV ≈ {weights.sum():.4f} [exact {exact:.4f}])", + font_size=10, +) +pl.camera_position = [ + (2.5, 2.0, 1.8), + (0.0, 0.0, 0.0), + (0.0, 0.0, 1.0), +] +pl.show() diff --git a/python/pyproject.toml b/python/pyproject.toml index fcb12ac..1512fcc 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -25,5 +25,6 @@ ci = ["mypy", "pytest-xdist", "cutcells[test]"] [tool.scikit-build] minimum-version = "0.5" cmake.minimum-version = "3.21.0" +build-dir = "build/{wheel_tag}" wheel.packages = ["cutcells"] wheel.license-files = ["../LICENSE"] diff --git a/python/tests/test_certification_refinement.py b/python/tests/test_certification_refinement.py new file mode 100644 index 0000000..0693702 --- /dev/null +++ b/python/tests/test_certification_refinement.py @@ -0,0 +1,561 @@ +import unittest + +import numpy as np + +import cutcells + + +def _single_triangle_mesh(): + coords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], dtype=np.float64) + connectivity = np.array([0, 1, 2], dtype=np.int32) + offsets = np.array([0, 3], dtype=np.int32) + cell_types = np.array([5], dtype=np.int32) # VTK_TRIANGLE + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=2) + + +def _single_tetra_mesh(): + coords = np.array( + [[0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0]], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3], dtype=np.int32) + offsets = np.array([0, 4], dtype=np.int32) + cell_types = np.array([10], dtype=np.int32) # VTK_TETRA + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=3) + + + + +class CertificationRefinementTests(unittest.TestCase): + def test_restrict_edge_bernstein_exact_matches_parent_evaluation(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] * X[0] + X[0] * X[1] + 0.25 * X[1] * X[1] - 0.1, + degree=2, + ) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + xi_a = np.array([0.1, 0.15], dtype=np.float64) + xi_b = np.array([0.7, 0.2], dtype=np.float64) + edge_coeffs = cutcells.restrict_edge_bernstein_exact( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + xi_a, + xi_b, + ) + + for t in np.linspace(0.0, 1.0, 9): + xi = (1.0 - t) * xi_a + t * xi_b + parent_val = cutcells.evaluate_bernstein( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + xi, + ) + edge_val = cutcells.evaluate_bernstein( + cutcells.CellType.interval, + ls_cell.bernstein_order, + np.asarray(edge_coeffs), + np.array([t], dtype=np.float64), + ) + self.assertAlmostEqual(edge_val, parent_val, places=10) + + def test_edge_endpoint_zero_semantics(self): + one_root_tag, split = cutcells.classify_edge_roots( + np.array([0.0, 0.0, 1.0], dtype=np.float64), + max_depth=18, + ) + self.assertEqual(one_root_tag, cutcells.EdgeRootTag.one_root) + self.assertIsNone(split) + + multi_tag, split = cutcells.classify_edge_roots( + np.array([0.0, -0.3, 0.4], dtype=np.float64), + max_depth=18, + ) + self.assertEqual(multi_tag, cutcells.EdgeRootTag.multiple_roots) + self.assertGreater(split, 0.0) + self.assertLess(split, 1.0) + + def test_green_refinement_on_multiple_root_edge(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: (X[0] - 0.2) * (X[0] - 0.8), + degree=2, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + tags = np.asarray(adapt.edge_root_tags(0)) + self.assertGreaterEqual( + np.count_nonzero(tags == cutcells.EdgeRootTag.multiple_roots.value), + 1, + ) + + changed = cutcells.refine_green_on_multiple_root_edges(adapt, 0) + self.assertTrue(changed) + self.assertEqual(adapt.num_vertices(), 4) + self.assertEqual(adapt.num_cells(), 2) + + def test_green_refinement_only_invalidates_new_edges(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: (X[0] - 0.2) * (X[0] - 0.8), + degree=2, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + before = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(before == cutcells.EdgeRootTag.not_classified.value), + 0, + ) + + changed = cutcells.refine_green_on_multiple_root_edges(adapt, 0) + self.assertTrue(changed) + + after_refine = np.asarray(adapt.edge_root_tags(0)) + self.assertGreater( + np.count_nonzero(after_refine == cutcells.EdgeRootTag.not_classified.value), + 0, + ) + self.assertGreater( + np.count_nonzero(after_refine != cutcells.EdgeRootTag.not_classified.value), + 0, + ) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + after_recertify = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(after_recertify == cutcells.EdgeRootTag.not_classified.value), + 0, + ) + + def test_red_refinement_on_ambiguous_triangle(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] * X[1] * (1.0 - X[0] - X[1]) - 1e-3, + degree=3, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + cutcells.classify_leaf_cells(adapt, ls_cell, 0) + self.assertEqual( + np.asarray(adapt.cell_cert_tags(0)).tolist(), + [cutcells.CellCertTag.ambiguous.value], + ) + + changed = cutcells.refine_red_on_ambiguous_cells(adapt, 0) + self.assertTrue(changed) + self.assertEqual(adapt.num_vertices(), 6) + self.assertEqual(adapt.num_cells(), 4) + self.assertEqual(adapt.num_edges(), 9) + + def test_triangle_cell_is_marked_ready_to_cut(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.3, + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + cutcells.classify_leaf_cells(adapt, ls_cell, 0) + self.assertEqual( + np.asarray(adapt.cell_cert_tags(0)).tolist(), + [cutcells.CellCertTag.ready_to_cut.value], + ) + + edge_tags = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.one_root.value), + 2, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.multiple_roots.value), + 0, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.zero.value), + 0, + ) + + def test_triangle_ready_to_cut_cell_is_replaced_by_polygonal_lut_cells(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.3, + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_refine_and_process_ready_cells(adapt, ls_cell, 0) + self.assertEqual(adapt.num_cells(), 2) + + cell_types = np.asarray(adapt.cell_types) + self.assertIn(cutcells.CellType.triangle.value, cell_types.tolist()) + self.assertIn(cutcells.CellType.quadrilateral.value, cell_types.tolist()) + + tags = np.asarray(adapt.cell_cert_tags(0)) + self.assertEqual( + sorted(np.unique(tags).tolist()), + sorted([ + cutcells.CellCertTag.negative.value, + cutcells.CellCertTag.positive.value, + ]), + ) + + def test_ready_to_cut_triangle_uses_linear_one_root_vertices(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] * X[0] + X[1] - 0.25, + degree=2, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_refine_and_process_ready_cells(adapt, ls_cell, 0) + + coords = np.asarray(adapt.vertex_coords).reshape(-1, 2) + source_edges = np.asarray(adapt.vertex_source_edge_id) + + expected_linear_root = np.array([0.25, 0.0]) + expected_second_root = np.array([0.0, 0.25]) + + d_linear = np.linalg.norm(coords - expected_linear_root, axis=1) + d_second = np.linalg.norm(coords - expected_second_root, axis=1) + + linear_id = int(np.argmin(d_linear)) + second_id = int(np.argmin(d_second)) + + self.assertLess(d_linear[linear_id], 1e-10) + self.assertLess(d_second[second_id], 1e-10) + self.assertGreaterEqual(int(source_edges[linear_id]), 0) + self.assertGreaterEqual(int(source_edges[second_id]), 0) + + def test_ho_cut_smoke_uses_certified_triangle_pipeline(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.3, + degree=1, + name="phi", + ) + + result = cutcells.ho_cut(mesh, ls) + + self.assertEqual(result.num_cut_cells, 1) + np.testing.assert_array_equal(np.asarray(result.parent_cell_ids), np.array([0])) + self.assertEqual(np.asarray(result.cell_domains).shape, (1, 1)) + + def test_cut_overload_supports_mesh_part_selection(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.3, + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls) + negative = result["phi < 0"] + interface = result["phi = 0"] + + self.assertEqual(result.num_cut_cells, 1) + np.testing.assert_array_equal(np.asarray(negative.cut_cell_ids), np.array([0])) + np.testing.assert_array_equal(np.asarray(interface.cut_cell_ids), np.array([0])) + + adapt = result.adapt_cell(0) + self.assertGreater(adapt.num_cells(), 0) + self.assertGreater(adapt.num_vertices(), 3) + + def test_zero_face_final_incidence_uses_face_to_cell_connectivity(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.5, + degree=1, + name="phi", + ) + + result = cutcells.ho_cut( + mesh, + ls, + ) + adapt = result.adapt_cell(0) + + zero_dims = np.asarray(adapt.zero_entity_dim, dtype=np.int32) + zero_ids = np.asarray(adapt.zero_entity_id, dtype=np.int32) + zero_face_ids = zero_ids[zero_dims == 2] + self.assertEqual(zero_face_ids.size, 1) + + face_id = int(zero_face_ids[0]) + face_to_cell = np.asarray(adapt.face_to_cell_connectivity, dtype=np.int32) + face_offsets = np.asarray(adapt.face_to_cell_offsets, dtype=np.int32) + incident_cells = face_to_cell[face_offsets[face_id]:face_offsets[face_id + 1]] + self.assertEqual(incident_cells.size, 2) + + cell_types = np.asarray(adapt.cell_types, dtype=np.int32) + self.assertEqual(cell_types[incident_cells].tolist(), [6, 6]) + + def test_cut_detects_quadratic_interior_tetra_intersection(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: ( + (X[0] - 0.2) * (X[0] - 0.2) + + (X[1] - 0.2) * (X[1] - 0.2) + + (X[2] - 0.2) * (X[2] - 0.2) + - 0.09 + ), + degree=2, + name="phi", + ) + + vertex_vals = np.array( + [ + (node[0] - 0.2) * (node[0] - 0.2) + + (node[1] - 0.2) * (node[1] - 0.2) + + (node[2] - 0.2) * (node[2] - 0.2) + - 0.09 + for node in np.asarray(mesh.coordinates) + ], + dtype=np.float64, + ) + self.assertTrue(np.all(vertex_vals > 0.0)) + + ls_cell = cutcells.make_cell_level_set(ls, 0) + self.assertLess(np.min(np.asarray(ls_cell.bernstein_coeffs)), 0.0) + + result = cutcells.cut(mesh, ls) + interface = result["phi = 0"] + + self.assertEqual(result.num_cut_cells, 1) + np.testing.assert_array_equal(np.asarray(interface.cut_cell_ids), np.array([0])) + + def test_certify_and_refine_runs_triangle_workflow(self): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] * X[1] * (1.0 - X[0] - X[1]) - 1e-3, + degree=3, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_and_refine(adapt, ls_cell, 0, max_iterations=2) + self.assertGreater(adapt.num_cells(), 1) + self.assertGreater(adapt.num_vertices(), 3) + + def test_green_refinement_on_multiple_root_tetra_edge(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: (X[0] - 0.1) * (X[0] - 0.6), + degree=2, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + tags = np.asarray(adapt.edge_root_tags(0)) + splits = np.asarray(adapt.edge_green_split_params(0)) + edge_connectivity = np.asarray(adapt.edge_connectivity) + edge_offsets = np.asarray(adapt.edge_offsets) + self.assertGreaterEqual( + np.count_nonzero(tags == cutcells.EdgeRootTag.multiple_roots.value), + 3, + ) + + split_edge = int(np.flatnonzero(tags == cutcells.EdgeRootTag.multiple_roots.value)[0]) + a, b = edge_connectivity[edge_offsets[split_edge]:edge_offsets[split_edge + 1]] + split_t = float(splits[split_edge]) + self.assertNotAlmostEqual(split_t, 0.5, places=6) + + vertex_coords = np.asarray(adapt.vertex_coords).reshape(-1, 3) + expected = (1.0 - split_t) * vertex_coords[a] + split_t * vertex_coords[b] + + changed = cutcells.refine_green_on_multiple_root_edges(adapt, 0) + self.assertTrue(changed) + self.assertEqual(adapt.num_vertices(), 5) + self.assertEqual(adapt.num_cells(), 2) + np.testing.assert_allclose( + np.asarray(adapt.vertex_coords).reshape(-1, 3)[-1], + expected, + atol=1e-12, + ) + + def test_tetra_cell_is_marked_ready_to_cut_for_triangular_interface(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] + X[2] - 0.2, + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + cutcells.classify_leaf_cells(adapt, ls_cell, 0) + self.assertEqual( + np.asarray(adapt.cell_cert_tags(0)).tolist(), + [cutcells.CellCertTag.ready_to_cut.value], + ) + + edge_tags = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.one_root.value), + 3, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.multiple_roots.value), + 0, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.zero.value), + 0, + ) + + def test_tetra_cell_is_marked_ready_to_cut_for_quadrilateral_interface(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - X[2] - 0.2, + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + cutcells.classify_leaf_cells(adapt, ls_cell, 0) + self.assertEqual( + np.asarray(adapt.cell_cert_tags(0)).tolist(), + [cutcells.CellCertTag.ready_to_cut.value], + ) + + edge_tags = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.one_root.value), + 4, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.multiple_roots.value), + 0, + ) + self.assertEqual( + np.count_nonzero(edge_tags == cutcells.EdgeRootTag.zero.value), + 0, + ) + + def test_tetra_ready_to_cut_cell_is_replaced_without_tetra_triangulation(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] + X[2] - 0.2, + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_refine_and_process_ready_cells(adapt, ls_cell, 0) + self.assertGreaterEqual(adapt.num_cells(), 2) + + cell_types = np.asarray(adapt.cell_types) + self.assertIn(cutcells.CellType.tetrahedron.value, cell_types.tolist()) + self.assertIn(cutcells.CellType.prism.value, cell_types.tolist()) + + tags = np.asarray(adapt.cell_cert_tags(0)) + self.assertEqual( + sorted(np.unique(tags).tolist()), + sorted([ + cutcells.CellCertTag.negative.value, + cutcells.CellCertTag.positive.value, + ]), + ) + + def test_zero_vertex_tetra_ready_to_cut_is_replaced_by_valid_tetrahedra(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - X[2], + degree=1, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_refine_and_process_ready_cells(adapt, ls_cell, 0) + + self.assertEqual(adapt.num_cells(), 3) + np.testing.assert_array_equal( + np.asarray(adapt.cell_types), + np.full(3, cutcells.CellType.tetrahedron.value, dtype=np.int32), + ) + + tags = np.asarray(adapt.cell_cert_tags(0)) + self.assertEqual( + sorted(np.unique(tags).tolist()), + sorted([ + cutcells.CellCertTag.negative.value, + cutcells.CellCertTag.positive.value, + ]), + ) + + connectivity = np.asarray(adapt.cell_connectivity, dtype=np.int32) + offsets = np.asarray(adapt.cell_offsets, dtype=np.int32) + for c in range(adapt.num_cells()): + cell_vertices = connectivity[offsets[c]:offsets[c + 1]] + self.assertEqual(len(set(cell_vertices.tolist())), 4) + + def test_certify_and_refine_recursively_splits_tetra_multiple_root_edges(self): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: (X[0] - 0.2) * (X[0] - 0.8), + degree=2, + ) + adapt = cutcells.make_adapt_cell(mesh, 0) + cutcells.build_edges(adapt) + ls_cell = cutcells.make_cell_level_set(ls, 0) + + cutcells.certify_and_refine(adapt, ls_cell, 0, max_iterations=8) + self.assertGreater(adapt.num_cells(), 2) + self.assertGreater(adapt.num_vertices(), 5) + + cutcells.classify_new_edges(adapt, ls_cell, 0) + tags = np.asarray(adapt.edge_root_tags(0)) + self.assertEqual( + np.count_nonzero(tags == cutcells.EdgeRootTag.multiple_roots.value), + 0, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_cut_mesh_parent_map_layout.py b/python/tests/test_cut_mesh_parent_map_layout.py new file mode 100644 index 0000000..9d126ac --- /dev/null +++ b/python/tests/test_cut_mesh_parent_map_layout.py @@ -0,0 +1,61 @@ +import numpy as np + +import cutcells + +VTK_QUAD = 9 + + +def _two_quad_strip_vtk(): + points = np.array( + [ + [0.0, 0.0, 0.0], # 0 + [1.0, 0.0, 0.0], # 1 + [2.0, 0.0, 0.0], # 2 + [0.0, 1.0, 0.0], # 3 + [1.0, 1.0, 0.0], # 4 + [2.0, 1.0, 0.0], # 5 + ], + dtype=float, + ) + + connectivity = np.array( + [ + 0, + 1, + 4, + 3, + 1, + 2, + 5, + 4, + ], + dtype=np.int32, + ) + offset = np.array([0, 4], dtype=np.int32) + vtk_type = np.array([VTK_QUAD, VTK_QUAD], dtype=np.int32) + + return points, connectivity, offset, vtk_type + + +def test_cut_mesh_parent_map_row_layout_smoke(): + points, connectivity, offset, vtk_type = _two_quad_strip_vtk() + + ls_vals = points[:, 1] - 0.5 + + cut_mesh = cutcells.cut_vtk_mesh( + ls_vals, + points.ravel(), + connectivity, + offset, + vtk_type, + "phi<0", + triangulate=False, + ) + + types = np.asarray(cut_mesh.types) + parent_map = np.asarray(cut_mesh.parent_map) + + assert types.size > 0 + assert parent_map.size == types.size + assert set(parent_map.tolist()).issubset({0, 1}) + assert 0 in parent_map and 1 in parent_map diff --git a/python/tests/test_cut_vtk_mesh_hexahedron_smoke.py b/python/tests/test_cut_vtk_mesh_hexahedron_smoke.py index 4aa3acb..d1ad5b0 100644 --- a/python/tests/test_cut_vtk_mesh_hexahedron_smoke.py +++ b/python/tests/test_cut_vtk_mesh_hexahedron_smoke.py @@ -43,7 +43,7 @@ def test_cut_vtk_mesh_single_hexahedron_smoke(): cut_mesh = cutcells.cut_vtk_mesh( ls_vals, - points, + points.ravel(), connectivity, offset, vtk_type, @@ -58,6 +58,6 @@ def test_cut_vtk_mesh_single_hexahedron_smoke(): assert coords[:, 1].min() >= -tol and coords[:, 1].max() <= 1.0 + tol assert coords[:, 2].min() >= -tol and coords[:, 2].max() <= 1.0 + tol - assert len(cut_mesh.types) > 0 + assert cut_mesh.types.size > 0 # Do not constrain emitted cell types here; the table-driven hex cutter may emit # hex/prism/pyramid/tet depending on the case. diff --git a/python/tests/test_cut_vtk_mesh_prism_smoke.py b/python/tests/test_cut_vtk_mesh_prism_smoke.py index a4ad210..0c0de17 100644 --- a/python/tests/test_cut_vtk_mesh_prism_smoke.py +++ b/python/tests/test_cut_vtk_mesh_prism_smoke.py @@ -34,7 +34,7 @@ def test_cut_vtk_mesh_single_prism_smoke(): cut_mesh = cutcells.cut_vtk_mesh( ls_vals, - points, + points.ravel(), connectivity, offset, vtk_type, @@ -48,4 +48,4 @@ def test_cut_vtk_mesh_single_prism_smoke(): assert coords[:, 1].min() >= -tol and coords[:, 1].max() <= 1.0 + tol assert coords[:, 2].min() >= -tol and coords[:, 2].max() <= 1.0 + tol - assert len(cut_mesh.types) > 0 + assert cut_mesh.types.size > 0 diff --git a/python/tests/test_cut_vtk_mesh_pyramid_smoke.py b/python/tests/test_cut_vtk_mesh_pyramid_smoke.py index a87aede..898b17c 100644 --- a/python/tests/test_cut_vtk_mesh_pyramid_smoke.py +++ b/python/tests/test_cut_vtk_mesh_pyramid_smoke.py @@ -33,7 +33,7 @@ def test_cut_vtk_mesh_single_pyramid_smoke(): cut_mesh = cutcells.cut_vtk_mesh( ls_vals, - points, + points.ravel(), connectivity, offset, vtk_type, @@ -47,4 +47,4 @@ def test_cut_vtk_mesh_single_pyramid_smoke(): assert coords[:, 1].min() >= -tol and coords[:, 1].max() <= 1.0 + tol assert coords[:, 2].min() >= -tol and coords[:, 2].max() <= 1.0 + tol - assert len(cut_mesh.types) > 0 + assert cut_mesh.types.size > 0 diff --git a/python/tests/test_cut_vtk_mesh_termination.py b/python/tests/test_cut_vtk_mesh_termination.py new file mode 100644 index 0000000..4f6be20 --- /dev/null +++ b/python/tests/test_cut_vtk_mesh_termination.py @@ -0,0 +1,63 @@ +# Copyright (c) 2026 ONERA +# Authors: Susanne Claus +# This file is part of CutCells +# +# SPDX-License-Identifier: MIT + +import time + +import numpy as np + +import cutcells + +VTK_HEXAHEDRON = 12 + + +def _single_unit_hex_vtk(): + points = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [1.0, 1.0, 1.0], + [0.0, 1.0, 1.0], + ], + dtype=float, + ) + + connectivity = np.array([0, 1, 2, 3, 4, 5, 6, 7], dtype=np.int32) + offset = np.array([0], dtype=np.int32) + vtk_type = np.array([VTK_HEXAHEDRON], dtype=np.int32) + + return points, connectivity, offset, vtk_type + + +def test_cut_vtk_mesh_terminates_quickly_on_small_mesh(): + points, connectivity, offset, vtk_type = _single_unit_hex_vtk() + ls_vals = points[:, 0] - 0.3 + + iterations = 200 + start = time.perf_counter() + + for _ in range(iterations): + cut_mesh = cutcells.cut_vtk_mesh( + ls_vals, + points.ravel(), + connectivity, + offset, + vtk_type, + "phi<0", + triangulate=True, + ) + + elapsed = time.perf_counter() - start + + assert cut_mesh.types.size > 0 + assert np.asarray(cut_mesh.vertex_coords).shape[1] == 3 + assert elapsed < 5.0, ( + f"cut_vtk_mesh appears too slow/non-terminating for a trivial mesh: " + f"{iterations} calls took {elapsed:.3f}s" + ) diff --git a/python/tests/test_cut_vtk_mesh_triangulate_flag.py b/python/tests/test_cut_vtk_mesh_triangulate_flag.py index 1925710..7dd7b4e 100644 --- a/python/tests/test_cut_vtk_mesh_triangulate_flag.py +++ b/python/tests/test_cut_vtk_mesh_triangulate_flag.py @@ -8,7 +8,8 @@ import cutcells -VTK_TRIANGLE = 5 +TRIANGLE = int(cutcells.CellType.triangle.value) +QUADRILATERAL = int(cutcells.CellType.quadrilateral.value) VTK_QUAD = 9 @@ -39,12 +40,24 @@ def test_cut_vtk_mesh_triangulate_flag_controls_output_types(): ls_vals = points[:, 0] - 0.3 cut_mesh_quads = cutcells.cut_vtk_mesh( - ls_vals, points, connectivity, offset, vtk_type, "phi<0", triangulate=False + ls_vals, + points.ravel(), + connectivity, + offset, + vtk_type, + "phi<0", + triangulate=False, ) - assert VTK_QUAD in list(cut_mesh_quads.types) + assert QUADRILATERAL in list(cut_mesh_quads.types) cut_mesh_tris = cutcells.cut_vtk_mesh( - ls_vals, points, connectivity, offset, vtk_type, "phi<0", triangulate=True + ls_vals, + points.ravel(), + connectivity, + offset, + vtk_type, + "phi<0", + triangulate=True, ) - assert VTK_QUAD not in list(cut_mesh_tris.types) - assert VTK_TRIANGLE in list(cut_mesh_tris.types) + assert QUADRILATERAL not in list(cut_mesh_tris.types) + assert TRIANGLE in list(cut_mesh_tris.types) diff --git a/python/tests/test_cutcell_original_info.py b/python/tests/test_cutcell_original_info.py index 23dfc7f..4257ddf 100644 --- a/python/tests/test_cutcell_original_info.py +++ b/python/tests/test_cutcell_original_info.py @@ -12,7 +12,7 @@ def test_cutcell_original_info(): # Cut it cut = cutcells.cut( - cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi<0", False + cutcells.CellType.quadrilateral, vertices.ravel(), 2, ls_values, "phi<0", False ) # For now, just verify the cut doesn't crash @@ -21,7 +21,7 @@ def test_cutcell_original_info(): # - cut._parent_vertex_coords matches vertices # - cut._parent_vertex_ids is [0,1,2,3] (or similar default) - assert len(cut.types) > 0, "Should produce some cut cells" + assert cut.types.size > 0, "Should produce some cut cells" assert cut.vertex_coords is not None, "Should have vertex coordinates" diff --git a/python/tests/test_cutcontext_edge_reuse.py b/python/tests/test_cutcontext_edge_reuse.py index 137a3f4..b9d1c90 100644 --- a/python/tests/test_cutcontext_edge_reuse.py +++ b/python/tests/test_cutcontext_edge_reuse.py @@ -25,11 +25,11 @@ def test_cutcontext_edge_reuse(): ls_values = np.array([-0.5, 0.5, 0.5, -0.5], dtype=float) cut = cutcells.cut( - cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi<0", False + cutcells.CellType.quadrilateral, vertices.ravel(), 2, ls_values, "phi<0", False ) # Basic sanity: cut should produce cells - assert len(cut.types) > 0 + assert cut.types.size > 0 assert cut.vertex_coords is not None # TODO: When CutContext is integrated, verify: diff --git a/python/tests/test_ho_part_straight_output.py b/python/tests/test_ho_part_straight_output.py new file mode 100644 index 0000000..ea6e5d3 --- /dev/null +++ b/python/tests/test_ho_part_straight_output.py @@ -0,0 +1,407 @@ +from pathlib import Path + +import numpy as np +import pytest + +import cutcells + + +def _single_tetra_mesh(): + coords = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3], dtype=np.int32) + offsets = np.array([0, 4], dtype=np.int32) + cell_types = np.array([10], dtype=np.int32) + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=3) + + +def _single_triangle_mesh(): + coords = np.array( + [ + [0.0, 0.0], + [1.0, 0.0], + [1.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2], dtype=np.int32) + offsets = np.array([0, 3], dtype=np.int32) + cell_types = np.array([5], dtype=np.int32) + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=2) + + +def _two_tetra_mesh(): + coords = np.array( + [ + [0.0, 0.0, 0.0], + [0.2, 0.0, 0.0], + [0.0, 0.2, 0.0], + [0.0, 0.0, 0.2], + [1.0, 0.0, 0.0], + [1.2, 0.0, 0.0], + [1.0, 0.2, 0.0], + [1.0, 0.0, 0.2], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3, 4, 5, 6, 7], dtype=np.int32) + offsets = np.array([0, 4, 8], dtype=np.int32) + cell_types = np.array([10, 10], dtype=np.int32) + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=3) + + +def _edge_key(xa: np.ndarray, xb: np.ndarray, digits: int = 12): + pa = tuple(np.round(np.asarray(xa, dtype=np.float64), digits)) + pb = tuple(np.round(np.asarray(xb, dtype=np.float64), digits)) + return tuple(sorted((pa, pb))) + + +def _vtk_edges(vtk_type: int): + if vtk_type == 5: # triangle + return ((0, 1), (1, 2), (2, 0)) + if vtk_type == 9: # quad + return ((0, 1), (1, 2), (2, 3), (3, 0)) + raise ValueError(f"Unsupported vtk_type {vtk_type} for boundary-edge extraction") + + +def _edge_counts(cut_mesh): + points = np.asarray(cut_mesh.vertex_coords, dtype=np.float64) + cells = np.asarray(cut_mesh.cells, dtype=np.int64) + vtk_types = np.asarray(cut_mesh.vtk_types, dtype=np.int64) + + counts = {} + cursor = 0 + for vtk_type in vtk_types: + num_verts = int(cells[cursor]) + local_ids = cells[cursor + 1 : cursor + 1 + num_verts] + cursor += num_verts + 1 + + for local_a, local_b in _vtk_edges(int(vtk_type)): + xa = points[int(local_ids[local_a])] + xb = points[int(local_ids[local_b])] + key = _edge_key(xa, xb) + counts[key] = counts.get(key, 0) + 1 + + return counts + + +def _assert_zero_edges_have_only_zero_vertices(adapt_cell): + zero_masks = np.asarray(adapt_cell.zero_mask_per_vertex, dtype=np.uint64) + edge_connectivity = np.asarray(adapt_cell.edge_connectivity, dtype=np.int32) + edge_offsets = np.asarray(adapt_cell.edge_offsets, dtype=np.int32) + zero_entity_dim = np.asarray(adapt_cell.zero_entity_dim, dtype=np.uint8) + zero_entity_id = np.asarray(adapt_cell.zero_entity_id, dtype=np.int32) + + for dim, entity_id in zip(zero_entity_dim, zero_entity_id): + if int(dim) != 1: + continue + verts = edge_connectivity[edge_offsets[entity_id] : edge_offsets[entity_id + 1]] + assert all(zero_masks[vertex_id] & 1 for vertex_id in verts) + + +def _assert_vertex_provenance_is_populated(adapt_cell, parent_cell_id: int): + parent_dim = np.asarray(adapt_cell.vertex_parent_dim, dtype=np.int8) + parent_id = np.asarray(adapt_cell.vertex_parent_id, dtype=np.int32) + parent_offsets = np.asarray(adapt_cell.vertex_parent_param_offset, dtype=np.int32) + parent_params = np.asarray(adapt_cell.vertex_parent_param, dtype=np.float64) + + assert len(parent_dim) == adapt_cell.num_vertices() + assert len(parent_id) == adapt_cell.num_vertices() + assert len(parent_offsets) == adapt_cell.num_vertices() + 1 + + for vertex_id, dim in enumerate(parent_dim): + dim = int(dim) + begin = int(parent_offsets[vertex_id]) + end = int(parent_offsets[vertex_id + 1]) + params = parent_params[begin:end] + assert dim >= 0 + assert int(parent_id[vertex_id]) >= 0 + assert len(params) == dim + if dim == adapt_cell.tdim: + assert int(parent_id[vertex_id]) == parent_cell_id + if dim == 1: + assert np.all(params >= -1.0e-12) + assert np.all(params <= 1.0 + 1.0e-12) + + +def test_triangle_lut_triangulated_quad_connects_uncut_edge_vertices_to_adjacent_roots(): + vertex_coordinates = np.array( + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + ], + dtype=np.float64, + ) + level_set_values = np.array([-0.7, 0.3, 0.3], dtype=np.float64) + + cut = cutcells.cut( + cutcells.CellType.triangle, + vertex_coordinates, + 2, + level_set_values, + "phi>0", + True, + ) + + parent_tokens = np.asarray(cut.vertex_parent_entity, dtype=np.int32) + connectivity = np.asarray(cut.connectivity, dtype=np.int32).reshape(-1, 3) + token_tris = {tuple(sorted(int(parent_tokens[v]) for v in tri)) for tri in connectivity} + + assert token_tris == { + tuple(sorted((101, 0, 1))), + tuple(sorted((1, 0, 2))), + tuple(sorted((102, 1, 2))), + } + + +def test_homeshpart_straight_output_bridge(tmp_path: Path): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.6, + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls) + negative = result["phi < 0"] + interface = result["phi = 0"] + + vis_cut = negative.visualization_mesh(mode="cut_only") + vis_full = negative.visualization_mesh(mode="full") + vis_interface = interface.visualization_mesh(mode="cut_only") + + assert len(np.asarray(vis_cut.types)) > 0 + assert len(np.asarray(vis_full.types)) >= len(np.asarray(vis_cut.types)) + assert len(np.asarray(vis_interface.types)) > 0 + assert np.unique(np.asarray(vis_cut.vtk_types)).tolist() == [13] + assert np.unique(np.asarray(vis_interface.vtk_types)).tolist() == [9] + + q_cut = negative.quadrature(order=3, mode="cut_only") + q_full = negative.quadrature(order=3, mode="full") + q_interface = interface.quadrature(order=3, mode="cut_only") + + assert q_cut.weights.sum() > 0.0 + assert q_full.weights.sum() >= q_cut.weights.sum() + assert q_interface.weights.sum() > 0.0 + + negative_path = tmp_path / "negative_full.vtu" + interface_path = tmp_path / "interface.vtu" + negative.write_vtu(str(negative_path), mode="full") + interface.write_vtu(str(interface_path), mode="cut_only") + + assert negative_path.exists() + assert interface_path.exists() + assert "Name=\"types\" format=\"ascii\">13 " in negative_path.read_text() + assert "Name=\"types\" format=\"ascii\">9 " in interface_path.read_text() + + +def test_mesh_part_string_selection_supports_volume_or(): + mesh = _two_tetra_mesh() + left = cutcells.create_level_set( + mesh, + lambda X: X[0] - 0.5, + degree=1, + name="left", + ) + right = cutcells.create_level_set( + mesh, + lambda X: 0.5 - X[0], + degree=1, + name="right", + ) + + result = cutcells.cut(mesh, [left, right]) + + left_part = result["left < 0"] + right_part = result["right < 0"] + union_part = result["left < 0 or right < 0"] + + np.testing.assert_array_equal(np.asarray(left_part.uncut_cell_ids), np.array([0])) + np.testing.assert_array_equal(np.asarray(right_part.uncut_cell_ids), np.array([1])) + np.testing.assert_array_equal( + np.asarray(union_part.uncut_cell_ids), + np.array([0, 1]), + ) + assert union_part.num_cut_cells == 0 + + +def test_mesh_part_string_selection_rejects_mixed_dimension_or(): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.6, + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls) + with pytest.raises(RuntimeError, match="same entity dimension"): + result["phi < 0 or phi = 0"] + + +def test_mesh_part_string_selection_supports_surface_or(): + mesh = _single_tetra_mesh() + phi = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.6, + degree=1, + name="phi", + ) + psi = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[2] - 0.6, + degree=1, + name="psi", + ) + + result = cutcells.cut(mesh, [phi, psi]) + union_part = result["phi = 0 or psi = 0"] + union_mesh = union_part.visualization_mesh(mode="cut_only") + + assert union_part.dim == 2 + np.testing.assert_array_equal(np.asarray(union_part.cut_cell_ids), np.array([0])) + assert len(np.asarray(union_mesh.types)) > 0 + + +def test_triangulated_tetra_prism_midpoints_keep_masks_and_source_edges(): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.6, + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls, triangulate=True) + adapt_cell = result.adapt_cell(0) + parent_cell_id = int(np.asarray(result.parent_cell_ids, dtype=np.int32)[0]) + + source_edges = np.asarray(adapt_cell.vertex_source_edge_id, dtype=np.int32) + zero_masks = np.asarray(adapt_cell.zero_mask_per_vertex, dtype=np.uint64) + negative_masks = np.asarray(adapt_cell.negative_mask_per_vertex, dtype=np.uint64) + vertex_coords = np.asarray(adapt_cell.vertex_coords, dtype=np.float64) + + # The first four vertices are the original tetra vertices. All vertices + # inserted by one-root localization or prism triangulation must retain an + # originating leaf edge so subsequent topology updates can preserve masks. + assert np.all(source_edges[4:] >= 0) + _assert_vertex_provenance_is_populated(adapt_cell, parent_cell_id) + + phi = vertex_coords[:, 0] + vertex_coords[:, 1] - 0.6 + for vertex_id in range(vertex_coords.shape[0]): + is_zero = bool(zero_masks[vertex_id] & 1) + is_negative = bool(negative_masks[vertex_id] & 1) + assert not (is_zero and is_negative) + if not is_zero: + assert is_negative == bool(phi[vertex_id] < 0.0) + + _assert_zero_edges_have_only_zero_vertices(adapt_cell) + + +def test_triangulated_triangle_quad_midpoint_keeps_masks_and_parent_edge(): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: 0.1 - 0.2 * X[0] - 0.1 * X[1], + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls, triangulate=True) + adapt_cell = result.adapt_cell(0) + parent_cell_id = int(np.asarray(result.parent_cell_ids, dtype=np.int32)[0]) + + source_edges = np.asarray(adapt_cell.vertex_source_edge_id, dtype=np.int32) + parent_dim = np.asarray(adapt_cell.vertex_parent_dim, dtype=np.int8) + parent_id = np.asarray(adapt_cell.vertex_parent_id, dtype=np.int32) + parent_offsets = np.asarray(adapt_cell.vertex_parent_param_offset, dtype=np.int32) + parent_params = np.asarray(adapt_cell.vertex_parent_param, dtype=np.float64) + zero_masks = np.asarray(adapt_cell.zero_mask_per_vertex, dtype=np.uint64) + negative_masks = np.asarray(adapt_cell.negative_mask_per_vertex, dtype=np.uint64) + vertex_coords = np.asarray(adapt_cell.vertex_coords, dtype=np.float64) + + # Vertices 3 and 4 are one-root vertices; vertex 5 is the triangulation + # midpoint on the uncut parent edge of the triangle-derived quadrilateral. + assert np.all(source_edges[3:] >= 0) + assert int(parent_dim[5]) == 1 + assert int(parent_id[5]) == int(source_edges[5]) + begin = int(parent_offsets[5]) + end = int(parent_offsets[6]) + np.testing.assert_allclose(parent_params[begin:end], [0.5]) + _assert_vertex_provenance_is_populated(adapt_cell, parent_cell_id) + + ls_cell = cutcells.make_cell_level_set(ls, parent_cell_id) + phi = np.array( + [ + cutcells.evaluate_bernstein( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + vertex_coords[vertex_id], + ) + for vertex_id in range(vertex_coords.shape[0]) + ], + dtype=np.float64, + ) + for vertex_id in range(vertex_coords.shape[0]): + is_zero = bool(zero_masks[vertex_id] & 1) + is_negative = bool(negative_masks[vertex_id] & 1) + assert not (is_zero and is_negative) + if not is_zero: + assert is_negative == bool(phi[vertex_id] < 0.0) + + _assert_zero_edges_have_only_zero_vertices(adapt_cell) + + +def test_interface_output_reflects_adaptcell_quad_leaf(): + mesh = _single_tetra_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] + X[1] - 0.6, + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls) + interface = result["phi = 0"] + + vis_interface = interface.visualization_mesh(mode="cut_only") + + quad_edges = _edge_counts(vis_interface) + + quad_boundary = {edge for edge, count in quad_edges.items() if count == 1} + + assert np.asarray(vis_interface.vtk_types).tolist() == [9] + assert len(quad_boundary) == 4 + + +def test_triangle_volume_output_reflects_adaptcell_quad_leaf(): + mesh = _single_triangle_mesh() + ls = cutcells.create_level_set( + mesh, + lambda X: 0.1 - 0.2 * X[0] - 0.1 * X[1], + degree=1, + name="phi", + ) + + result = cutcells.cut(mesh, ls) + negative = result["phi < 0"] + + vis_base = negative.visualization_mesh(mode="cut_only") + q_base = negative.quadrature(order=3, mode="cut_only") + + assert np.asarray(vis_base.vtk_types).tolist() == [9] + assert np.asarray(vis_base.vertex_coords).shape[0] == 4 + assert q_base.weights.sum() > 0.0 diff --git a/python/tests/test_level_set_interpolate.py b/python/tests/test_level_set_interpolate.py new file mode 100644 index 0000000..d5f7609 --- /dev/null +++ b/python/tests/test_level_set_interpolate.py @@ -0,0 +1,123 @@ +import numpy as np +import pytest + +import cutcells + + +def _make_mesh(cell_family: str): + if cell_family == "triangle": + coordinates = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2], dtype=np.int32) + offsets = np.array([0, 3], dtype=np.int32) + cell_types = np.array([5], dtype=np.int32) # VTK_TRIANGLE + tdim = 2 + elif cell_family == "tetra": + coordinates = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3], dtype=np.int32) + offsets = np.array([0, 4], dtype=np.int32) + cell_types = np.array([10], dtype=np.int32) # VTK_TETRA + tdim = 3 + else: + raise ValueError(f"Unsupported cell_family={cell_family}") + + return cutcells.MeshView( + coordinates=coordinates, + connectivity=connectivity, + offsets=offsets, + cell_types=cell_types, + tdim=tdim, + ) + + +@pytest.mark.parametrize("cell_family", ["triangle", "tetra"]) +@pytest.mark.parametrize("degree", [2, 3, 4]) +def test_interpolate_level_set_calls_callback_once_and_sets_discrete_data(cell_family, degree): + mesh = _make_mesh(cell_family) + callback_count = 0 + callback_shapes = [] + + def phi(X): + nonlocal callback_count + callback_count += 1 + callback_shapes.append(X.shape) + return X[:, 0] + 2.0 * X[:, 1] - 0.5 * X[:, 2] + + ls = cutcells.interpolate_level_set(mesh, phi, degree=degree) + + assert callback_count == 1 + assert callback_shapes[0] == (ls.mesh_data.num_dofs(), mesh.gdim) + + assert ls.has_mesh_data() is True + assert ls.has_dof_values() is True + assert ls.mesh_data.degree == degree + assert len(ls.dof_values) == ls.mesh_data.num_dofs() + + coords = np.asarray(ls.mesh_data.dof_coordinates) + np.testing.assert_allclose(np.asarray(ls.dof_values), phi(coords)) + + +def test_create_level_set_function_from_mesh_data_and_values(): + dof_coordinates = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.5, 0.5, 0.0], + [0.0, 0.5, 0.0], + [0.5, 0.0, 0.0], + ], + dtype=np.float64, + ) + cell_dofs = np.array([0, 1, 2, 3, 4, 5], dtype=np.int32) + cell_offsets = np.array([0, 6], dtype=np.int32) + cell_types = np.array([5], dtype=np.int32) + dof_values = np.linspace(-1.0, 1.0, 6, dtype=np.float64) + + mesh_data = cutcells.create_level_set_mesh_data( + dof_coordinates=dof_coordinates, + cell_dofs=cell_dofs, + cell_offsets=cell_offsets, + degree=2, + tdim=2, + cell_types=cell_types, + ) + ls = cutcells.create_level_set_function(mesh_data, dof_values) + + assert ls.has_mesh_data() is True + assert ls.has_dof_values() is True + np.testing.assert_allclose(np.asarray(ls.dof_values), dof_values) + + +def test_legacy_level_set_constructor_paths_still_work(): + ls_callable = cutcells.LevelSetFunction( + value=lambda x: float(x[0] - 0.25), + gdim=3, + ) + assert ls_callable.has_value() is True + assert ls_callable.has_nodal_values() is False + assert ls_callable.has_mesh_data() is False + assert ls_callable.has_dof_values() is False + value = ls_callable.value(np.array([0.5, 0.0, 0.0], dtype=np.float64)) + assert value == pytest.approx(0.25) + + nodal = np.array([0.1, -0.2, 0.3], dtype=np.float64) + ls_nodal = cutcells.LevelSetFunction(nodal_values=nodal, gdim=3) + assert ls_nodal.has_nodal_values() is True + assert ls_nodal.value_at_node(1) == pytest.approx(-0.2) + assert ls_nodal.has_mesh_data() is False + assert ls_nodal.has_dof_values() is False diff --git a/python/tests/test_level_set_mesh_data.py b/python/tests/test_level_set_mesh_data.py new file mode 100644 index 0000000..0a229b0 --- /dev/null +++ b/python/tests/test_level_set_mesh_data.py @@ -0,0 +1,138 @@ +import numpy as np + +import cutcells + + +def test_create_level_set_mesh_data_triangle_p2_shared_dofs(): + coords = np.array( + [ + [0.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + [1.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 1, 3, 2], dtype=np.int32) + offsets = np.array([0, 3, 6], dtype=np.int32) + cell_types = np.array([5, 5], dtype=np.int32) # VTK_TRIANGLE + + mesh = cutcells.MeshView(coords, connectivity, offsets, cell_types, 2) + data = cutcells.create_level_set_mesh_data(mesh, 2) + + assert data.gdim == 2 + assert data.tdim == 2 + assert data.degree == 2 + assert data.num_cells() == 2 + assert data.num_dofs() == 9 + + cell_offsets = np.asarray(data.cell_offsets) + cell_dofs = np.asarray(data.cell_dofs) + dof_coords = np.asarray(data.dof_coordinates) + dof_parent_dim = np.asarray(data.dof_parent_dim) + dof_parent_id = np.asarray(data.dof_parent_id) + dof_parent_param = np.asarray(data.dof_parent_param) + dof_parent_param_offset = np.asarray(data.dof_parent_param_offset) + + first_cell = cell_dofs[cell_offsets[0] : cell_offsets[1]] + second_cell = cell_dofs[cell_offsets[1] : cell_offsets[2]] + + # Basix Triangle6 ordering: + # vertices (0,1,2), then edge dofs on (1,2), (0,2), (0,1). + expected_first = np.array( + [ + [0.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + [0.5, 0.5], + [0.0, 0.5], + [0.5, 0.0], + ] + ) + np.testing.assert_allclose(dof_coords[first_cell], expected_first) + + shared = set(first_cell).intersection(set(second_cell)) + assert len(shared) == 3 + + # First 4 dofs are parent vertices; remaining 5 are edge-born. + np.testing.assert_array_equal(dof_parent_dim[:4], np.zeros(4, dtype=np.int8)) + np.testing.assert_array_equal(dof_parent_id[:4], np.arange(4, dtype=np.int32)) + np.testing.assert_array_equal(dof_parent_dim[4:], np.ones(5, dtype=np.int8)) + assert dof_parent_param_offset.shape[0] == data.num_dofs() + 1 + + # The shared midpoint on the diagonal edge should have one edge parameter at t=0.5. + shared_edge_dof = first_cell[3] + pb = dof_parent_param_offset[shared_edge_dof] + pe = dof_parent_param_offset[shared_edge_dof + 1] + np.testing.assert_allclose(dof_parent_param[pb:pe], np.array([0.5])) + + +def test_create_level_set_mesh_data_from_arrays(): + dof_coordinates = np.array( + [ + [0.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + ], + dtype=np.float64, + ) + cell_dofs = np.array([0, 1, 2], dtype=np.int32) + cell_offsets = np.array([0, 3], dtype=np.int32) + cell_types = np.array([5], dtype=np.int32) + + data = cutcells.create_level_set_mesh_data( + dof_coordinates, cell_dofs, cell_offsets, degree=1, tdim=2, cell_types=cell_types + ) + + assert data.gdim == 2 + assert data.tdim == 2 + assert data.degree == 1 + np.testing.assert_array_equal(np.asarray(data.cell_dofs), cell_dofs) + np.testing.assert_array_equal(np.asarray(data.cell_offsets), cell_offsets) + np.testing.assert_allclose(np.asarray(data.dof_coordinates), dof_coordinates) + assert np.asarray(data.dof_parent_dim).size == 0 + + +def test_make_cell_level_set_matches_generated_mesh_data_without_provenance(): + coords = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3], dtype=np.int32) + offsets = np.array([0, 4], dtype=np.int32) + cell_types = np.array([10], dtype=np.int32) # VTK_TETRA + mesh = cutcells.MeshView(coords, connectivity, offsets, cell_types, 3) + + ls = cutcells.create_level_set( + mesh, + lambda X: X[0] * X[0] + 0.5 * X[1] * X[2] - 0.25 * X[2] + 0.1, + degree=2, + name="phi", + ) + ls_cell_generated = cutcells.make_cell_level_set(ls, 0) + + mesh_data = ls.mesh_data + mesh_data_from_arrays = cutcells.create_level_set_mesh_data( + np.array(mesh_data.dof_coordinates), + np.array(mesh_data.cell_dofs), + np.array(mesh_data.cell_offsets), + degree=mesh_data.degree, + tdim=mesh_data.tdim, + cell_types=np.array([10], dtype=np.int32), + ) + ls_from_arrays = cutcells.create_level_set_function( + mesh_data_from_arrays, + np.array(ls.dof_values), + name="phi", + ) + ls_cell_from_arrays = cutcells.make_cell_level_set(ls_from_arrays, 0) + + np.testing.assert_allclose( + np.asarray(ls_cell_from_arrays.bernstein_coeffs), + np.asarray(ls_cell_generated.bernstein_coeffs), + ) diff --git a/python/tests/test_level_set_vtu.py b/python/tests/test_level_set_vtu.py new file mode 100644 index 0000000..5487c2a --- /dev/null +++ b/python/tests/test_level_set_vtu.py @@ -0,0 +1,231 @@ +from pathlib import Path +import xml.etree.ElementTree as ET + +import numpy as np +import pytest + +import cutcells + + +TRIANGLE_BASIX_TO_VTK = { + 2: [0, 1, 2, 5, 3, 4], + 3: [0, 1, 2, 7, 8, 3, 4, 6, 5, 9], + 4: [0, 1, 2, 9, 10, 11, 3, 4, 5, 8, 7, 6, 12, 13, 14], +} + +TETRA_BASIX_TO_VTK = { + 2: [0, 1, 2, 3, 9, 6, 8, 7, 5, 4], + 3: [0, 1, 2, 3, 14, 15, 8, 9, 13, 12, 10, 11, 6, 7, 4, 5, 18, 16, 17, 19], + 4: [ + 0, + 1, + 2, + 3, + 19, + 20, + 21, + 10, + 11, + 12, + 18, + 17, + 16, + 13, + 14, + 15, + 7, + 8, + 9, + 4, + 5, + 6, + 28, + 29, + 30, + 23, + 24, + 22, + 25, + 27, + 26, + 31, + 33, + 32, + 34, + ], +} + + +def _read_dataarray(path: Path, name: str) -> str: + root = ET.parse(path).getroot() + for data_array in root.iter("DataArray"): + if data_array.attrib.get("Name") == name: + return (data_array.text or "").strip() + raise AssertionError(f"DataArray '{name}' not found in {path}") + + +def _find_dataarray(path: Path, name: str) -> ET.Element: + root = ET.parse(path).getroot() + for data_array in root.iter("DataArray"): + if data_array.attrib.get("Name") == name: + return data_array + raise AssertionError(f"DataArray '{name}' not found in {path}") + + +def _make_single_cell_level_set(cell_family: str, degree: int): + if cell_family == "triangle": + local_dofs = (degree + 1) * (degree + 2) // 2 + vtk_type = 5 + tdim = 2 + elif cell_family == "tetra": + local_dofs = (degree + 1) * (degree + 2) * (degree + 3) // 6 + vtk_type = 10 + tdim = 3 + else: + raise ValueError(f"Unsupported cell_family={cell_family}") + + dof_coordinates = np.zeros((local_dofs, 3), dtype=np.float64) + dof_coordinates[:, 0] = np.arange(local_dofs, dtype=np.float64) + cell_dofs = np.arange(local_dofs, dtype=np.int32) + cell_offsets = np.array([0, local_dofs], dtype=np.int32) + cell_types = np.array([vtk_type], dtype=np.int32) + dof_values = np.arange(local_dofs, dtype=np.float64) + 0.125 + + mesh_data = cutcells.create_level_set_mesh_data( + dof_coordinates=dof_coordinates, + cell_dofs=cell_dofs, + cell_offsets=cell_offsets, + degree=degree, + tdim=tdim, + cell_types=cell_types, + ) + ls = cutcells.create_level_set_function(mesh_data, dof_values) + return ls, dof_values + + +@pytest.mark.parametrize("degree", [2, 3, 4]) +def test_write_level_set_vtu_triangle_connectivity_permutation(tmp_path, degree): + ls, dof_values = _make_single_cell_level_set("triangle", degree) + output = tmp_path / f"triangle_p{degree}.vtu" + cutcells.write_level_set_vtu(str(output), ls, field_name="phi") + + connectivity = [int(x) for x in _read_dataarray(output, "connectivity").split()] + types = [int(x) for x in _read_dataarray(output, "types").split()] + phi = np.fromstring(_read_dataarray(output, "phi"), sep=" ") + + assert connectivity == TRIANGLE_BASIX_TO_VTK[degree] + assert types == [69] # VTK_LAGRANGE_TRIANGLE + np.testing.assert_allclose(phi, dof_values) + + +@pytest.mark.parametrize("degree", [2, 3, 4]) +def test_write_level_set_vtu_tetra_connectivity_permutation(tmp_path, degree): + ls, dof_values = _make_single_cell_level_set("tetra", degree) + output = tmp_path / f"tetra_p{degree}.vtu" + cutcells.write_level_set_vtu(str(output), ls, field_name="phi") + + connectivity = [int(x) for x in _read_dataarray(output, "connectivity").split()] + types = [int(x) for x in _read_dataarray(output, "types").split()] + phi = np.fromstring(_read_dataarray(output, "phi"), sep=" ") + + assert connectivity == TETRA_BASIX_TO_VTK[degree] + assert types == [71] # VTK_LAGRANGE_TETRAHEDRON + np.testing.assert_allclose(phi, dof_values) + + +def test_write_level_set_vtu_xml_matches_vtk_expectations(tmp_path): + ls, _ = _make_single_cell_level_set("triangle", 3) + output = tmp_path / "triangle_p3.vtu" + cutcells.write_level_set_vtu(str(output), ls, field_name="phi") + + root = ET.parse(output).getroot() + assert root.attrib["type"] == "UnstructuredGrid" + assert root.attrib["version"] == "0.1" + assert root.attrib["byte_order"] in {"LittleEndian", "BigEndian"} + + types = _find_dataarray(output, "types") + assert types.attrib["type"] == "UInt8" + + +def test_write_vtk_generic_writer_triangle(tmp_path): + output = tmp_path / "triangle_linear.vtu" + points = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], dtype=np.float64) + connectivity = np.array([0, 1, 2], dtype=np.int32) + offsets = np.array([0, 3], dtype=np.int32) + element_types = np.array([cutcells.CellType.triangle.value], dtype=np.int32) + + cutcells.write_vtk( + str(output), + points.reshape(-1), + connectivity, + offsets, + element_types, + gdim=2, + ) + + assert [int(x) for x in _read_dataarray(output, "connectivity").split()] == [0, 1, 2] + assert [int(x) for x in _read_dataarray(output, "offsets").split()] == [3] + assert [int(x) for x in _read_dataarray(output, "types").split()] == [5] + + +@pytest.mark.parametrize( + ("cell_family", "degree", "expected_type", "expected_local_dofs"), + [ + ("triangle", 2, 69, 6), + ("triangle", 3, 69, 10), + ("triangle", 4, 69, 15), + ("tetra", 2, 71, 10), + ("tetra", 3, 71, 20), + ("tetra", 4, 71, 35), + ], +) +def test_write_level_set_vtu_cell_widths_match_degree( + tmp_path, cell_family, degree, expected_type, expected_local_dofs +): + ls, _ = _make_single_cell_level_set(cell_family, degree) + output = tmp_path / f"{cell_family}_p{degree}.vtu" + cutcells.write_level_set_vtu(str(output), ls, field_name="phi") + + connectivity = [int(x) for x in _read_dataarray(output, "connectivity").split()] + offsets = [int(x) for x in _read_dataarray(output, "offsets").split()] + types = [int(x) for x in _read_dataarray(output, "types").split()] + + prev = 0 + widths = [] + for off in offsets: + widths.append(off - prev) + prev = off + + assert widths == [expected_local_dofs] + assert len(connectivity) == expected_local_dofs + assert types == [expected_type] + + +@pytest.mark.parametrize("cell_family", ["triangle", "tetra"]) +@pytest.mark.parametrize("degree", [2, 3, 4]) +def test_write_level_set_vtu_roundtrip_with_pyvista(tmp_path, cell_family, degree): + pv = pytest.importorskip("pyvista") + + if cell_family == "triangle": + grid = cutcells.rectangle_triangle_mesh(-1.0, -1.0, 1.0, 1.0, 5, 5) + mesh = cutcells.mesh_from_pyvista(grid, tdim=2) + expected_type = 69 + else: + grid = cutcells.box_tetrahedron_mesh(-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 4, 4, 4) + mesh = cutcells.mesh_from_pyvista(grid, tdim=3) + expected_type = 71 + + def phi(X): + return np.sqrt((X[:, 0] - 0.15) ** 2 + (X[:, 1] + 0.1) ** 2 + (X[:, 2] - 0.05) ** 2) - 0.55 + + ls = cutcells.interpolate_level_set(mesh, phi, degree=degree) + output = tmp_path / f"{cell_family}_p{degree}.vtu" + cutcells.write_level_set_vtu(str(output), ls, field_name="phi") + + out_grid = pv.read(output) + assert out_grid.n_points == ls.mesh_data.num_dofs() + assert "phi" in out_grid.point_data + assert len(np.asarray(out_grid.point_data["phi"])) == ls.mesh_data.num_dofs() + assert np.all(np.asarray(out_grid.celltypes) == expected_type) + assert all(out_grid.GetCell(i).GetOrder() == degree for i in range(out_grid.n_cells)) + assert all(not out_grid.GetCell(i).IsLinear() for i in range(out_grid.n_cells)) diff --git a/python/tests/test_mapping.py b/python/tests/test_mapping.py new file mode 100644 index 0000000..c5d1cc0 --- /dev/null +++ b/python/tests/test_mapping.py @@ -0,0 +1,223 @@ +# Copyright (c) 2024 ONERA +# Authors: Susanne Claus +# This file is part of CutCells +# +# SPDX-License-Identifier: MIT +"""Tests for mapping.h / compute_physical_cut_vertices & complete_from_physical. + +Design: + - CutCells normally cuts in PHYSICAL space: + _vertex_coords from cut() = physical coordinates. + To obtain the reference coordinates: call complete_from_physical(). + -> copies _vertex_coords -> _vertex_coords_phys (physical) + -> pulls back to fill _vertex_coords with reference coords + + - complete_from_physical (the standard workflow): + After: _vertex_coords = reference, _vertex_coords_phys = physical. + + - compute_physical_cut_vertices (reference-space cut workflow): + If _vertex_coords were already in reference coords, push them to physical. + After: _vertex_coords = unchanged (reference), _vertex_coords_phys = physical. + +Coverage: + - complete_from_physical: pull-back produces correct reference coordinates. + - compute_physical_cut_vertices: push-forward from reference to physical. + - round-trip consistency. +""" + +import numpy as np +import pytest +import cutcells + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _unit_triangle(): + """Unit right triangle (0,0),(1,0),(0,1) in physical/reference space.""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0], dtype=np.float64) + return vertex_coords, 2 + + +def _scaled_triangle(sx, sy): + """Triangle (0,0),(sx,0),(0,sy) — physical coords differ from reference.""" + vertex_coords = np.array([0.0, 0.0, sx, 0.0, 0.0, sy], dtype=np.float64) + return vertex_coords, 2 + + +def _standard_tet(): + """Standard tetrahedron: (0,0,0),(1,0,0),(0,1,0),(0,0,1).""" + vertex_coords = np.array( + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=np.float64 + ) + return vertex_coords, 3 + + +# --------------------------------------------------------------------------- +# 1. complete_from_physical (standard workflow: cut in physical space) +# --------------------------------------------------------------------------- + + +class TestCompleteFromPhysical: + """cut() is performed with physical vertex coordinates. + complete_from_physical() sets _vertex_coords_phys = copy of _vertex_coords + (i.e. the physical cut coords), then pulls back to fill _vertex_coords with + reference coords.""" + + def test_unit_triangle_identity(self): + """For the unit triangle (identity map) ref == phys after call.""" + vertex_coords, gdim = _unit_triangle() + ls = np.array([0.5, -0.5, 0.5], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, gdim, ls, "phi<0", True + ) + assert cut_cell.vertex_coords_phys.size == 0, ( + "vertex_coords_phys must be empty before complete_from_physical" + ) + phys_from_cutter = cut_cell.vertex_coords.copy() + + cutcells.complete_from_physical(cut_cell) + + vc_ref = cut_cell.vertex_coords + vc_phys = cut_cell.vertex_coords_phys + + # physical should be what came out of the cutter + np.testing.assert_allclose( + vc_phys, phys_from_cutter.reshape(-1, gdim), atol=1e-14 + ) + # reference == physical for identity map + np.testing.assert_allclose( + vc_ref, phys_from_cutter.reshape(-1, gdim), atol=1e-14 + ) + + def test_scaled_triangle_pull_back(self): + """Scaled triangle (3×2): pull-back yields ref = phys / scale.""" + sx, sy = 3.0, 2.0 + vertex_coords, gdim = _scaled_triangle(sx, sy) + ls = np.array([0.1, -0.1, 0.2], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, gdim, ls, "phi<0", True + ) + phys_from_cutter = cut_cell.vertex_coords.copy() + + cutcells.complete_from_physical(cut_cell) + + vc_ref = cut_cell.vertex_coords + vc_phys = cut_cell.vertex_coords_phys + + np.testing.assert_allclose( + vc_phys, phys_from_cutter.reshape(-1, gdim), atol=1e-13 + ) + # For affine map v0=0, J=diag(sx,sy): ref = phys / [sx, sy] + expected_ref = phys_from_cutter.reshape(-1, gdim) / np.array([sx, sy]) + np.testing.assert_allclose(vc_ref, expected_ref, atol=1e-13) + + def test_tetrahedron_pull_back(self): + """Standard tet (identity map): ref == phys after complete_from_physical.""" + vertex_coords, gdim = _standard_tet() + ls = np.array([0.1, -0.1, 0.2, 0.2], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.tetrahedron, vertex_coords, gdim, ls, "phi<0", True + ) + phys_from_cutter = cut_cell.vertex_coords.copy() + + cutcells.complete_from_physical(cut_cell) + + np.testing.assert_allclose( + cut_cell.vertex_coords_phys, phys_from_cutter.reshape(-1, gdim), atol=1e-14 + ) + np.testing.assert_allclose( + cut_cell.vertex_coords, phys_from_cutter.reshape(-1, gdim), atol=1e-14 + ) + + def test_phys_preserved_after_first_call(self): + """complete_from_physical preserves the original physical cut coords in + vertex_coords_phys and converts vertex_coords to reference coordinates. + Calling it a second time would further pull back (not idempotent) — verify + that only the first call gives meaningful physical-space vertex_coords_phys.""" + vertex_coords, gdim = _scaled_triangle(2.5, 1.5) + ls = np.array([0.5, -0.5, 0.5], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, gdim, ls, "phi<0", True + ) + phys_original = cut_cell.vertex_coords.copy() # physical from cut() + + cutcells.complete_from_physical(cut_cell) + + # After first call: _vertex_coords_phys == original physical cut coords + np.testing.assert_allclose( + cut_cell.vertex_coords_phys, + phys_original.reshape(-1, gdim), + atol=1e-13, + err_msg="vertex_coords_phys must equal original physical cut coords", + ) + # _vertex_coords must have been pulled back + expected_ref = phys_original.reshape(-1, gdim) / np.array([2.5, 1.5]) + np.testing.assert_allclose(cut_cell.vertex_coords, expected_ref, atol=1e-13) + + +# --------------------------------------------------------------------------- +# 2. compute_physical_cut_vertices (reference-frame cut workflow) +# --------------------------------------------------------------------------- + + +class TestComputePhysicalCutVertices: + """compute_physical_cut_vertices pushes _vertex_coords (treated as reference) + to physical space using the affine map defined by _parent_vertex_coords. + + For the standard physical-space cutting workflow, _vertex_coords are already + physical, so compute_physical_cut_vertices would push them *again* (incorrect + usage). The correct function in that case is complete_from_physical. + + Here we test the correct usage: cutting with reference (unit) vertex coordinates + and then pushing forward to physical.""" + + def test_unit_triangle_identity(self): + """For identity map push-forward gives phys == ref.""" + vertex_coords, gdim = _unit_triangle() + ls = np.array([0.5, -0.5, 0.5], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, gdim, ls, "phi<0", True + ) + assert cut_cell.vertex_coords_phys.size == 0 + + cutcells.compute_physical_cut_vertices(cut_cell) + + vc_ref = cut_cell.vertex_coords.reshape(-1, gdim) + vc_phys = cut_cell.vertex_coords_phys + np.testing.assert_allclose(vc_phys, vc_ref, atol=1e-14) + + def test_tetrahedron_identity(self): + """Standard tet (identity map): phys == ref.""" + vertex_coords, gdim = _standard_tet() + ls = np.array([0.1, -0.1, 0.2, 0.2], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.tetrahedron, vertex_coords, gdim, ls, "phi<0", True + ) + cutcells.compute_physical_cut_vertices(cut_cell) + + vc_ref = cut_cell.vertex_coords.reshape(-1, gdim) + vc_phys = cut_cell.vertex_coords_phys + np.testing.assert_allclose(vc_phys, vc_ref, atol=1e-14) + + def test_idempotent(self): + """Calling compute_physical_cut_vertices twice gives the same physical coords.""" + vertex_coords, gdim = _unit_triangle() + ls = np.array([0.5, -0.5, 0.5], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, gdim, ls, "phi<0", True + ) + cutcells.compute_physical_cut_vertices(cut_cell) + phys1 = cut_cell.vertex_coords_phys.copy() + cutcells.compute_physical_cut_vertices(cut_cell) + phys2 = cut_cell.vertex_coords_phys + np.testing.assert_array_equal(phys1, phys2) diff --git a/python/tests/test_mesh_utils.py b/python/tests/test_mesh_utils.py new file mode 100644 index 0000000..45924ad --- /dev/null +++ b/python/tests/test_mesh_utils.py @@ -0,0 +1,29 @@ +import pytest +import cutcells + + +def test_structured_triangle_mesh_view(): + mesh = cutcells.structured_triangle_mesh_view(-1, -1, 1, 1, 5, 4) + assert mesh.tdim == 2 + assert mesh.gdim == 2 + assert mesh.num_nodes() == 20 + assert mesh.num_cells() == 24 + + +def test_safe_part_name(): + assert cutcells.safe_part_name("phi_left = 0 and phi_right < 0") == "phi_lefteq0andphi_rightlt0" + + +def test_rectangle_triangle(): + grid = cutcells.rectangle_triangle_mesh(-1, -1, 1, 1, 10, 10) + assert grid.n_points == 100 + assert grid.n_cells > 0 + assert int(grid.celltypes[0]) == 5 # VTK_TRIANGLE + + +def test_mesh_from_pyvista_infers_tdim(): + grid = cutcells.rectangle_triangle_mesh(-1, -1, 1, 1, 5, 5) + mesh = cutcells.mesh_from_pyvista(grid) + assert mesh.tdim == 2 + assert mesh.gdim == 3 + assert mesh.num_cells() == grid.n_cells diff --git a/python/tests/test_quad_table_sanity.py b/python/tests/test_quad_table_sanity.py index b450ce0..ab2f459 100644 --- a/python/tests/test_quad_table_sanity.py +++ b/python/tests/test_quad_table_sanity.py @@ -30,13 +30,13 @@ def test_quad_table_sanity(): ls_values = np.array([-0.5, 0.5, 0.5, -0.5], dtype=float) cut = cutcells.cut( - cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi<0", False + cutcells.CellType.quadrilateral, vertices.ravel(), 2, ls_values, "phi<0", False ) # Should produce valid output - assert len(cut.types) > 0 + assert cut.types.size > 0 assert cut.vertex_coords is not None # Basic validation passed if we got here - print(f"Produced {len(cut.types)} subcells") + print(f"Produced {cut.types.size} subcells") print("✓ test_quad_table_sanity passed") diff --git a/python/tests/test_quadrature.py b/python/tests/test_quadrature.py new file mode 100644 index 0000000..72d69c4 --- /dev/null +++ b/python/tests/test_quadrature.py @@ -0,0 +1,309 @@ +# Copyright (c) 2024 ONERA +# Authors: Susanne Claus +# This file is part of CutCells +# +# SPDX-License-Identifier: MIT +"""Tests for quadrature.h / make_quadrature. + +Coverage: + - QuadratureRules struct (tdim, points, weights, offset, parent_map). + - make_quadrature: full-cell case integrates to correct area/volume. + - make_quadrature: cut-cell case integrates subdomain area. + - Batch interface: multiple cut cells, offset/parent_map metadata. + - Cell types: triangle, tetrahedron, hexahedron. +""" + +import numpy as np +import pytest +import cutcells + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_and_enrich( + cell_type, vertex_coords, gdim, ls_values, cut_type, triangulate=True +): + """Cut (physical coords), complete frame (complete_from_physical), return enriched CutCell.""" + cut_cell = cutcells.cut( + cell_type, vertex_coords, gdim, ls_values, cut_type, triangulate + ) + cutcells.complete_from_physical(cut_cell) + return cut_cell + + +def _integrate(cut_cells_list, order=3): + """Return QuadratureRules and summed weight for cut_cells_list.""" + qr = cutcells.make_quadrature(cut_cells_list, order) + return qr, np.sum(qr.weights) + + +# --------------------------------------------------------------------------- +# 1. QuadratureRules struct +# --------------------------------------------------------------------------- + + +class TestQuadratureRulesStruct: + def test_default_empty(self): + qr = cutcells._cutcellscpp.QuadratureRules_float64() + assert qr.tdim == 0 + assert qr.points.size == 0 + assert qr.weights.size == 0 + assert qr.offset.size == 0 + assert qr.parent_map.size == 0 + + def test_types(self): + """After make_quadrature the arrays have expected dtypes.""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0], dtype=np.float64) + ls = np.array([0.1, -0.1, 0.2], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + qr = cutcells.make_quadrature([cut_cell], 2) + assert qr.points.dtype == np.float64 + assert qr.weights.dtype == np.float64 + assert qr.offset.dtype == np.int32 + assert qr.parent_map.dtype == np.int32 + + +# --------------------------------------------------------------------------- +# 2. Full-cell integration (no cut) +# --------------------------------------------------------------------------- + + +class TestFullCellIntegration: + """Barely-intersected cells (epsilon level-set) must integrate to ~full area/volume. + A level-set like [-1,-1,...,+eps] creates a tiny cut but leaves essentially the full + interior intact, so the integral must be close to the full cell measure.""" + + def test_triangle_full_cell_area(self): + """Unit right triangle has area 0.5.""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0], dtype=np.float64) + # Tiny positive at one vertex: interior ~= full triangle + eps = 1e-4 + ls = np.array([-1.0, -1.0, eps], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=4) + np.testing.assert_allclose(total, 0.5, rtol=1e-3) + + def test_triangle_scaled_area(self): + """Scaled triangle (3×2) has area 3.0.""" + vertex_coords = np.array([0.0, 0.0, 3.0, 0.0, 0.0, 2.0], dtype=np.float64) + eps = 1e-4 + ls = np.array([-1.0, -1.0, eps], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=4) + np.testing.assert_allclose(total, 3.0, rtol=1e-3) + + def test_tetrahedron_full_cell_volume(self): + """Standard tet has volume 1/6.""" + vertex_coords = np.array( + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], + dtype=np.float64, + ) + eps = 1e-4 + ls = np.array([-1.0, -1.0, -1.0, eps], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.tetrahedron, vertex_coords, 3, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=3) + np.testing.assert_allclose(total, 1.0 / 6.0, rtol=1e-3) + + def test_hexahedron_full_cell_volume(self): + """Unit cube has volume 1.0. + + VTK hexahedron vertex ordering: + v0=(0,0,0) v1=(1,0,0) v2=(1,1,0) v3=(0,1,0) + v4=(0,0,1) v5=(1,0,1) v6=(1,1,1) v7=(0,1,1) + """ + vertex_coords = np.array( + [ + 0.0, + 0.0, + 0.0, # v0 + 1.0, + 0.0, + 0.0, # v1 + 1.0, + 1.0, + 0.0, # v2 + 0.0, + 1.0, + 0.0, # v3 + 0.0, + 0.0, + 1.0, # v4 + 1.0, + 0.0, + 1.0, # v5 + 1.0, + 1.0, + 1.0, # v6 + 0.0, + 1.0, + 1.0, # v7 + ], + dtype=np.float64, + ) + eps = 1e-4 + # v6=(1,1,1) has eps>0; all others negative → phi<0 is almost the full cube + ls = np.array([-1.0] * 6 + [eps] + [-1.0], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.hexahedron, vertex_coords, 3, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=3) + np.testing.assert_allclose(total, 1.0, rtol=1e-3) + + +# --------------------------------------------------------------------------- +# 3. Cut-cell integration +# --------------------------------------------------------------------------- + + +class TestCutCellIntegration: + """The interior cut of a known fraction must integrate to that fraction.""" + + def test_triangle_half_cut(self): + """Level set x - 0.5 = 0 cuts the unit triangle. + The interior phi<0 part has area 0.5 * 0.5 * (1 - 0.5^2) ... compute + analytically: triangle with vertices (0,0),(0.5,0),(0,1),(... tricky). + Use the known vol from test_triangle.py instead: ls=[0.1,-0.1,0.2] -> 1/12.""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0], dtype=np.float64) + ls = np.array([0.1, -0.1, 0.2], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=5) + np.testing.assert_allclose(total, 1.0 / 12.0, rtol=1e-9) + + def test_tetrahedron_cut(self): + """Known tet cut: ls=[0.1,-0.1,0.2,0.2] interior vol = 4/27.""" + vertex_coords = np.array( + [1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0], + dtype=np.float64, + ) + ls = np.array([0.1, -0.1, 0.2, 0.2], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.tetrahedron, vertex_coords, 3, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=5) + np.testing.assert_allclose(total, 4.0 / 27.0, rtol=1e-8) + + @pytest.mark.parametrize("order", [1, 2, 3, 4, 5]) + def test_triangle_order_convergence(self, order): + """All orders should give the exact answer for a linear level-set cut + because the sub-cells are simplices (exact for polynomials of any order).""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0], dtype=np.float64) + ls = np.array([0.1, -0.1, 0.2], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + _, total = _integrate([cut_cell], order=order) + np.testing.assert_allclose( + total, 1.0 / 12.0, rtol=1e-9, err_msg=f"order={order} failed" + ) + + +# --------------------------------------------------------------------------- +# 4. Batch interface +# --------------------------------------------------------------------------- + + +class TestBatchIntegration: + """make_quadrature([c1, c2, ...]) must produce correct offset/parent_map.""" + + def _make_triangle_cells(self, n_cells): + """n_cells copies of a half-cut unit triangle.""" + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0], dtype=np.float64) + ls = np.array([0.5, -0.5, 0.5], dtype=np.float64) + cells = [] + for _ in range(n_cells): + c = _make_and_enrich( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0" + ) + cells.append(c) + return cells + + def test_offset_length(self): + cells = self._make_triangle_cells(4) + qr = cutcells.make_quadrature(cells, 3) + assert len(qr.offset) == len(cells) + 1, "offset length must be num_rules + 1" + assert qr.offset[0] == 0, "offset[0] must be 0" + assert qr.offset[-1] == len(qr.weights), ( + "last offset must equal total number of quadrature points" + ) + + def test_parent_map_length(self): + cells = self._make_triangle_cells(3) + qr = cutcells.make_quadrature(cells, 3) + assert len(qr.parent_map) == len(cells) + + def test_parent_map_values(self): + cells = self._make_triangle_cells(3) + qr = cutcells.make_quadrature(cells, 3) + np.testing.assert_array_equal(qr.parent_map, [0, 1, 2]) + + def test_batch_sum_equals_individual_sum(self): + cells = self._make_triangle_cells(5) + qr_batch = cutcells.make_quadrature(cells, 3) + batch_total = np.sum(qr_batch.weights) + + individual_total = 0.0 + for c in cells: + qr_i = cutcells.make_quadrature([c], 3) + individual_total += np.sum(qr_i.weights) + + np.testing.assert_allclose(batch_total, individual_total, rtol=1e-14) + + def test_points_shape(self): + cells = self._make_triangle_cells(2) + qr = cutcells.make_quadrature(cells, 3) + total_pts = qr.offset[-1] + assert qr.points.size == total_pts * qr.tdim, ( + "flat points array size must be total_pts * tdim" + ) + + def test_tdim_triangle(self): + cells = self._make_triangle_cells(1) + qr = cutcells.make_quadrature(cells, 2) + assert qr.tdim == 2, "tdim for triangle parent should be 2" + + def test_tdim_tetrahedron(self): + vertex_coords = np.array( + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], + dtype=np.float64, + ) + ls = np.array([0.1, -0.1, 0.2, 0.2], dtype=np.float64) + cut_cell = _make_and_enrich( + cutcells.CellType.tetrahedron, vertex_coords, 3, ls, "phi<0" + ) + qr = cutcells.make_quadrature([cut_cell], 2) + assert qr.tdim == 3 + + +# --------------------------------------------------------------------------- +# 5. Float32 variant +# --------------------------------------------------------------------------- + + +class TestFloat32: + """Smoke-test the float32 code path.""" + + def test_triangle_full_cell_float32(self): + vertex_coords = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0], dtype=np.float32) + ls = np.array([0.1, -0.1, 0.2], dtype=np.float32) + cut_cell = cutcells.cut( + cutcells.CellType.triangle, vertex_coords, 2, ls, "phi<0", True + ) + cutcells.complete_from_physical(cut_cell) + qr = cutcells.make_quadrature([cut_cell], 3) + total = np.sum(qr.weights) + np.testing.assert_allclose(total, 1.0 / 12.0, rtol=1e-4) + assert qr.points.dtype == np.float32 + assert qr.weights.dtype == np.float32 diff --git a/python/tests/test_quadrilateral.py b/python/tests/test_quadrilateral.py index 7809167..61fe6dc 100644 --- a/python/tests/test_quadrilateral.py +++ b/python/tests/test_quadrilateral.py @@ -8,8 +8,8 @@ import cutcells -VTK_TRIANGLE = 5 -VTK_QUAD = 9 +TRIANGLE = int(cutcells.CellType.triangle.value) +QUADRILATERAL = int(cutcells.CellType.quadrilateral.value) def _square_vertices(): @@ -23,13 +23,13 @@ def test_quadrilateral_opposite_corners_disconnected(): cut_inside = cutcells.cut( cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi<0", False ) - assert cut_inside.types == [VTK_TRIANGLE, VTK_TRIANGLE] + assert list(cut_inside.types) == [TRIANGLE, TRIANGLE] assert np.isclose(cut_inside.volume(), 0.25) cut_outside = cutcells.cut( cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi>0", False ) - assert cut_outside.types == [VTK_QUAD, VTK_QUAD] + assert list(cut_outside.types) == [QUADRILATERAL, QUADRILATERAL] assert np.isclose(cut_outside.volume(), 0.75) @@ -40,11 +40,11 @@ def test_quadrilateral_pentagon_splits_triangle_quad(): cut_inside = cutcells.cut( cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi<0", False ) - assert cut_inside.types == [VTK_TRIANGLE, VTK_QUAD] + assert list(cut_inside.types) == [TRIANGLE, QUADRILATERAL] assert np.isclose(cut_inside.volume(), 0.875) cut_outside = cutcells.cut( cutcells.CellType.quadrilateral, vertices, 2, ls_values, "phi>0", False ) - assert cut_outside.types == [VTK_TRIANGLE] + assert list(cut_outside.types) == [TRIANGLE] assert np.isclose(cut_outside.volume(), 0.125) diff --git a/python/tests/test_quadrilateral_ambiguity.py b/python/tests/test_quadrilateral_ambiguity.py index e7918f0..226bbd2 100644 --- a/python/tests/test_quadrilateral_ambiguity.py +++ b/python/tests/test_quadrilateral_ambiguity.py @@ -48,16 +48,17 @@ def test_asymptotic_decider_scale_invariant_and_different_diagonals(): def _segments_from_cutcell(cut_cell): coords = np.asarray(cut_cell.vertex_coords) - conn = list(cut_cell.connectivity) + conn = np.asarray(cut_cell.connectivity) + offsets = np.asarray(cut_cell.offsets) segments = [] - i = 0 - while i < len(conn): - n = int(conn[i]) + for i in range(len(offsets) - 1): + begin = int(offsets[i]) + end = int(offsets[i + 1]) + n = end - begin assert n == 2 - a = int(conn[i + 1]) - b = int(conn[i + 2]) + a = int(conn[begin]) + b = int(conn[begin + 1]) segments.append((coords[a], coords[b])) - i += 1 + n return segments @@ -124,12 +125,14 @@ def test_quad_ambiguous_connected_variant_keeps_quads(ls_values, expected_mask): ) types = list(inside.types) - # VTK_QUAD = 9 - assert types == [9, 9] - - conn = list(inside.connectivity) - i = 0 - for _ in range(2): - assert int(conn[i]) == 4 - i += 5 - assert i == len(conn) + assert types == [ + int(cutcells.CellType.quadrilateral.value), + int(cutcells.CellType.quadrilateral.value), + ] + + conn = np.asarray(inside.connectivity) + offsets = np.asarray(inside.offsets) + assert len(offsets) == 3 + for i in range(2): + assert int(offsets[i + 1] - offsets[i]) == 4 + assert int(offsets[-1]) == len(conn) diff --git a/python/tests/test_runtime_quadrature.py b/python/tests/test_runtime_quadrature.py new file mode 100644 index 0000000..f5f3f31 --- /dev/null +++ b/python/tests/test_runtime_quadrature.py @@ -0,0 +1,182 @@ +"""Pytest suite for runtime_quadrature and physical_points.""" + +import numpy as np +import pytest +import cutcells + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _full_cell_rules(pts, conn_indices, vtk_type_id, triangulate, order=4): + """Return QuadratureRules for a fully-inside cell.""" + pts_arr = np.array(pts, dtype=np.float64).flatten() + conn = np.array(conn_indices, dtype=np.int32) + offs = np.array([0, len(conn_indices)], dtype=np.int32) + vtype = np.array([vtk_type_id], dtype=np.int32) + ls = np.full(len(pts), -1.0, dtype=np.float64) # all inside + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtype, "phi<0", triangulate, order + ) + phys = cutcells.physical_points(rules, pts_arr, conn, offs, vtype) + return rules, phys.reshape(-1, 3) + + +# --------------------------------------------------------------------------- +# Test data: (name, pts, conn, vtk_id, expected_vol) +# --------------------------------------------------------------------------- + +INTERVAL_PTS = [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]] +TRI_PTS = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] +TET_PTS = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] +QUAD_PTS = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]] +HEX_PTS = [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [1.0, 1.0, 1.0], + [0.0, 1.0, 1.0], +] +PRISM_PTS = [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [0.0, 1.0, 1.0], +] +PYR_PTS = [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.5, 0.5, 1.0], +] + +CELL_CASES = [ + # (name, pts, conn, vtk_id, expected_volume) + ("interval", INTERVAL_PTS, [0, 1], 3, 2.0), + ("triangle", TRI_PTS, [0, 1, 2], 5, 0.5), + ("tetrahedron", TET_PTS, [0, 1, 2, 3], 10, 1.0 / 6.0), + ("quad", QUAD_PTS, [0, 1, 2, 3], 9, 1.0), + ("hexahedron", HEX_PTS, list(range(8)), 12, 1.0), + ("prism", PRISM_PTS, list(range(6)), 13, 0.5), + ("pyramid", PYR_PTS, list(range(5)), 14, 1.0 / 3.0), +] + + +# --------------------------------------------------------------------------- +# Parametrized volume tests +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("name,pts,conn,vtk_id,expected_vol", CELL_CASES) +@pytest.mark.parametrize("triangulate", [False, True]) +def test_full_cell_volume(name, pts, conn, vtk_id, expected_vol, triangulate): + """Sum of quadrature weights equals the physical volume of the cell.""" + rules, _ = _full_cell_rules(pts, conn, vtk_id, triangulate) + assert abs(rules.weights.sum() - expected_vol) < 1e-9, ( + f"{name} triangulate={triangulate}: " + f"got {rules.weights.sum()}, expected {expected_vol}" + ) + + +@pytest.mark.parametrize("name,pts,conn,vtk_id,expected_vol", CELL_CASES) +@pytest.mark.parametrize("triangulate", [False, True]) +def test_full_cell_physical_points_shape( + name, pts, conn, vtk_id, expected_vol, triangulate +): + """physical_points returns array of shape (nq, 3).""" + rules, phys = _full_cell_rules(pts, conn, vtk_id, triangulate) + assert phys.shape == (len(rules.weights), 3) + + +# --------------------------------------------------------------------------- +# Cut cell tests (tet only — requires tdim == gdim == 3) +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "cut_type,frac", + [ + ("phi<0", 1.0 / 6.0), # cut from one corner; vol < full tet + ("phi>0", 1.0 / 6.0), # complement + ], +) +def test_cut_tet_volume_sign(cut_type, frac): + """Cut tet quadrature produces sensible weights (sum < full volume).""" + pts_arr = np.array(TET_PTS, dtype=np.float64).flatten() + ls = np.array([-1.0, 1.0, 1.0, 1.0], dtype=np.float64) # 1 vertex inside + conn = np.array([0, 1, 2, 3], dtype=np.int32) + offs = np.array([0, 4], dtype=np.int32) + vtype = np.array([10], dtype=np.int32) + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtype, cut_type, True, 3 + ) + s = rules.weights.sum() + assert 0 < s < frac + 1e-10 + + +def test_cut_tet_physical_points(): + """physical_points for a cut tet has correct shape.""" + pts_arr = np.array(TET_PTS, dtype=np.float64).flatten() + ls = np.array([-1.0, 1.0, 1.0, 1.0], dtype=np.float64) + conn = np.array([0, 1, 2, 3], dtype=np.int32) + offs = np.array([0, 4], dtype=np.int32) + vtype = np.array([10], dtype=np.int32) + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtype, "phi<0", True, 3 + ) + phys = cutcells.physical_points(rules, pts_arr, conn, offs, vtype) + assert phys.reshape(-1, 3).shape[1] == 3 + + +def test_physical_points_rejects_non_vtk_parent_type_ids(): + """physical_points expects VTK parent ids, not internal CutCells type ids.""" + pts_arr = np.array(TET_PTS, dtype=np.float64).flatten() + ls = np.full(4, -1.0, dtype=np.float64) + conn = np.array([0, 1, 2, 3], dtype=np.int32) + offs = np.array([0, 4], dtype=np.int32) + vtk_tetra = np.array([10], dtype=np.int32) + internal_tetra = np.array([3], dtype=np.int32) + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtk_tetra, "phi<0", False, 2 + ) + + with pytest.raises(ValueError, match="dimension does not match"): + cutcells.physical_points(rules, pts_arr, conn, offs, internal_tetra) + + +# --------------------------------------------------------------------------- +# Fully outside → empty result +# --------------------------------------------------------------------------- + + +def test_fully_outside_empty(): + """A cell with all ls > 0 and cut_type='phi<0' produces zero quadrature points.""" + pts_arr = np.array(TET_PTS, dtype=np.float64).flatten() + ls = np.full(4, 1.0, dtype=np.float64) # all outside + conn = np.array([0, 1, 2, 3], dtype=np.int32) + offs = np.array([0, 4], dtype=np.int32) + vtype = np.array([10], dtype=np.int32) + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtype, "phi<0", True, 3 + ) + assert len(rules.weights) == 0 + + +def test_fully_inside_all_ls_negative(): + """A cell with all ls < 0 and cut_type='phi<0' gives the full volume.""" + pts_arr = np.array(TET_PTS, dtype=np.float64).flatten() + ls = np.full(4, -1.0, dtype=np.float64) + conn = np.array([0, 1, 2, 3], dtype=np.int32) + offs = np.array([0, 4], dtype=np.int32) + vtype = np.array([10], dtype=np.int32) + rules = cutcells.runtime_quadrature( + ls, pts_arr, conn, offs, vtype, "phi<0", True, 4 + ) + assert abs(rules.weights.sum() - 1.0 / 6.0) < 1e-9 diff --git a/python/tests/test_tetra_triangulated_cut_edges.py b/python/tests/test_tetra_triangulated_cut_edges.py new file mode 100644 index 0000000..7f95a8b --- /dev/null +++ b/python/tests/test_tetra_triangulated_cut_edges.py @@ -0,0 +1,185 @@ +import numpy as np + +import cutcells + + +EDGE_PATTERNS = { + 5: ((0, 1), (1, 2), (2, 0)), # triangle + 9: ((0, 1), (1, 2), (2, 3), (3, 0)), # quad + 10: ((0, 1), (1, 2), (2, 0), (0, 3), (1, 3), (2, 3)), # tet + 13: ( + (0, 1), + (1, 2), + (2, 0), + (3, 4), + (4, 5), + (5, 3), + (0, 3), + (1, 4), + (2, 5), + ), # prism / wedge +} + + +def _single_tetra_mesh(): + coords = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ], + dtype=np.float64, + ) + connectivity = np.array([0, 1, 2, 3], dtype=np.int32) + offsets = np.array([0, 4], dtype=np.int32) + cell_types = np.array([10], dtype=np.int32) + return cutcells.MeshView(coords, connectivity, offsets, cell_types, tdim=3), coords + + +def _edge_key(xa: np.ndarray, xb: np.ndarray, digits: int = 12): + pa = tuple(np.round(np.asarray(xa, dtype=np.float64), digits)) + pb = tuple(np.round(np.asarray(xb, dtype=np.float64), digits)) + return tuple(sorted((pa, pb))) + + +def _collect_unique_edges(cut_cell): + points = np.asarray(cut_cell.vertex_coords, dtype=np.float64) + cells = np.asarray(cut_cell.cells, dtype=np.int64) + vtk_types = np.asarray(cut_cell.vtk_types, dtype=np.int64) + + edges = {} + cursor = 0 + for vtk_type in vtk_types: + num_verts = int(cells[cursor]) + local_ids = cells[cursor + 1 : cursor + 1 + num_verts] + cursor += num_verts + 1 + + for local_a, local_b in EDGE_PATTERNS[int(vtk_type)]: + xa = points[int(local_ids[local_a])] + xb = points[int(local_ids[local_b])] + edges[_edge_key(xa, xb)] = ( + np.array(xa, dtype=np.float64, copy=True), + np.array(xb, dtype=np.float64, copy=True), + ) + + return edges + + +def _classify_new_edges(ls_cell, base_cut, triangulated_cut): + base_edges = _collect_unique_edges(base_cut) + triangulated_edges = _collect_unique_edges(triangulated_cut) + + records = [] + for key in sorted(set(triangulated_edges) - set(base_edges)): + xa, xb = triangulated_edges[key] + coeffs = np.asarray( + cutcells.restrict_edge_bernstein_exact( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + np.asarray(xa, dtype=np.float64), + np.asarray(xb, dtype=np.float64), + ), + dtype=np.float64, + ) + tag, _ = cutcells.classify_edge_roots( + coeffs, + zero_tol=1.0e-12, + sign_tol=1.0e-12, + max_depth=20, + ) + phi_a = float( + cutcells.evaluate_bernstein( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + np.asarray(xa, dtype=np.float64), + ) + ) + phi_b = float( + cutcells.evaluate_bernstein( + ls_cell.cell_type, + ls_cell.bernstein_order, + np.asarray(ls_cell.bernstein_coeffs), + np.asarray(xb, dtype=np.float64), + ) + ) + has_zero_endpoint = abs(phi_a) <= 1.0e-12 or abs(phi_b) <= 1.0e-12 + records.append( + { + "tag": tag, + "has_zero_endpoint": has_zero_endpoint, + "has_interior_root": tag == cutcells.EdgeRootTag.multiple_roots + or (tag == cutcells.EdgeRootTag.one_root and not has_zero_endpoint), + } + ) + + return records + + +def test_triangulated_tetra_cut_edges_are_classified_against_zero_level_set(): + mesh, coords = _single_tetra_mesh() + nodal_phi = coords[:, 0] + coords[:, 1] - 0.6 + ls = cutcells.create_level_set(mesh, lambda X: X[0] + X[1] - 0.6, degree=1, name="phi") + ls_cell = cutcells.make_cell_level_set(ls, 0) + + expected = { + "phi<0": { + "base_types": [13], + "triangulated_types": [10, 10, 10, 10], + "new_edge_count": 7, + "zero_tag_count": 1, + "zero_endpoint_count": 5, + "interior_root_count": 0, + }, + "phi=0": { + "base_types": [9], + "triangulated_types": [5, 5], + "new_edge_count": 1, + "zero_tag_count": 1, + "zero_endpoint_count": 1, + "interior_root_count": 0, + }, + "phi>0": { + "base_types": [13], + "triangulated_types": [10, 10, 10, 10], + "new_edge_count": 7, + "zero_tag_count": 1, + "zero_endpoint_count": 5, + "interior_root_count": 0, + }, + } + + for expr, exp in expected.items(): + base_cut = cutcells.cut( + cutcells.CellType.tetrahedron, + coords.ravel(), + 3, + nodal_phi, + expr, + False, + ) + triangulated_cut = cutcells.cut( + cutcells.CellType.tetrahedron, + coords.ravel(), + 3, + nodal_phi, + expr, + True, + ) + + assert np.asarray(base_cut.vtk_types).tolist() == exp["base_types"] + assert np.asarray(triangulated_cut.vtk_types).tolist() == exp["triangulated_types"] + + edge_records = _classify_new_edges(ls_cell, base_cut, triangulated_cut) + assert len(edge_records) == exp["new_edge_count"] + assert sum(record["tag"] == cutcells.EdgeRootTag.zero for record in edge_records) == exp[ + "zero_tag_count" + ] + assert sum(record["has_zero_endpoint"] for record in edge_records) == exp[ + "zero_endpoint_count" + ] + assert sum(record["has_interior_root"] for record in edge_records) == exp[ + "interior_root_count" + ] diff --git a/python/tests/test_tetra_triangulation_analysis.py b/python/tests/test_tetra_triangulation_analysis.py new file mode 100644 index 0000000..f22286d --- /dev/null +++ b/python/tests/test_tetra_triangulation_analysis.py @@ -0,0 +1,59 @@ +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +import cutcells + +pytestmark = pytest.mark.skipif( + not hasattr(cutcells, "analyze_all_cases"), + reason="triangulation_analysis helpers are not packaged", +) + +CellType = cutcells.CellType +analyze_all_cases = getattr(cutcells, "analyze_all_cases", None) +summarize_analysis = getattr(cutcells, "summarize_analysis", None) + + +def test_tetra_parent_summary_excludes_zero_only_edges_from_interior_root_count(): + records, summary = analyze_all_cases(CellType.tetrahedron) + + assert summary["cases"] == 42 + assert summary["total_new_edges"] == 218 + assert summary["new_edges_with_interior_roots"] == 0 + assert summary["new_edges_with_zero_tag"] == 18 + assert summary["new_edges_with_zero_endpoint"] == 122 + assert summary["total_new_simplices"] == 116 + assert summary["negative_new_simplices"] == 0 + assert summary["degenerate_new_simplices"] == 0 + + +def test_tetra_parent_interface_quads_have_no_interior_root_edges_or_negative_triangles(): + records, _ = analyze_all_cases(CellType.tetrahedron) + interface_summary = summarize_analysis([record for record in records if record["expr"] == "phi=0"]) + + assert interface_summary["cases"] == 14 + assert interface_summary["source_type_counts"] == {"quadrilateral": 12} + assert interface_summary["new_edges_with_interior_roots"] == 0 + assert interface_summary["new_edges_with_zero_tag"] == 6 + assert interface_summary["new_edges_with_zero_endpoint"] == 6 + assert interface_summary["negative_new_simplices"] == 0 + assert interface_summary["degenerate_new_simplices"] == 0 + + +def test_tetra_parent_prism_volume_cases_have_only_endpoint_roots_and_positive_tets(): + records, _ = analyze_all_cases(CellType.tetrahedron) + + negative_summary = summarize_analysis([record for record in records if record["expr"] == "phi<0"]) + positive_summary = summarize_analysis([record for record in records if record["expr"] == "phi>0"]) + + for summary in (negative_summary, positive_summary): + assert summary["cases"] == 14 + assert summary["source_type_counts"] == {"prism": 52} + assert summary["new_edges_with_interior_roots"] == 0 + assert summary["new_edges_with_zero_tag"] == 6 + assert summary["new_edges_with_zero_endpoint"] == 58 + assert summary["negative_new_simplices"] == 0 + assert summary["degenerate_new_simplices"] == 0 diff --git a/python/tests/test_triangle.py b/python/tests/test_triangle.py index 9062a20..c30d768 100644 --- a/python/tests/test_triangle.py +++ b/python/tests/test_triangle.py @@ -7,6 +7,8 @@ import numpy as np import pytest +TRIANGLE = int(cutcells.CellType.triangle.value) + level_set_values = [(np.array([0.1,-0.1,0.2]),1./12.), (np.array([-0.1,0.1,0.2]),1./12.), (np.array([0.1,0.1,-0.2]),2./9.), @@ -45,4 +47,24 @@ def test_triangle_exterior(ls_values, vol_ex): cut_cell = cutcells.cut(cell_type, vertex_coordinates, gdim, ls_values, "phi>0", triangulate) vol = cut_cell.volume() - assert np.isclose(vol,vol_ex) \ No newline at end of file + assert np.isclose(vol,vol_ex) + + +def test_triangle_quad_midpoint_split(): + vertex_coordinates = np.array([0.,0.,1.,0.,1.,1.], dtype=np.float64) + ls_values = np.array([0.1,-0.1,-0.2], dtype=np.float64) + + cut_cell = cutcells.cut( + cutcells.CellType.triangle, + vertex_coordinates, + 2, + ls_values, + "phi<0", + True, + ) + + coords = np.asarray(cut_cell.vertex_coords, dtype=np.float64) + + assert list(cut_cell.types) == [TRIANGLE, TRIANGLE, TRIANGLE] + assert coords.shape == (5, 2) + assert np.any(np.all(np.isclose(coords, np.array([1.0, 0.5])), axis=1)) diff --git a/tablegen/cutcells_tablegen/emit_c_headers.py b/tablegen/cutcells_tablegen/emit_c_headers.py index b379b4a..c794652 100644 --- a/tablegen/cutcells_tablegen/emit_c_headers.py +++ b/tablegen/cutcells_tablegen/emit_c_headers.py @@ -249,7 +249,9 @@ def _emit_tet_like_header( end = arrays["sub_element_offset"][i + 1] num_subcells_per_case.append(end - start) - lines.append(f"// Number of subcells produced for each case ({table_kind} volume)") + lines.append( + f"// Number of subcells produced for each case ({table_kind} volume)" + ) lines.append( f"constexpr int num_subcells_{table_kind}[{n_cases}] = " + _format_int_list(num_subcells_per_case) diff --git a/tablegen/scripts/gen_quadrature_tables.py b/tablegen/scripts/gen_quadrature_tables.py new file mode 100644 index 0000000..6858cd4 --- /dev/null +++ b/tablegen/scripts/gen_quadrature_tables.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Offline Basix quadrature table generator for CutCells. + +Usage: + python gen_quadrature_tables.py [MAX_ORDER [OUTPUT_DIR]] + +Defaults: + MAX_ORDER = 10 + OUTPUT_DIR = /cpp/src/generated + +Environment variables (to override Basix lookup paths): + BASIX_PYPATH – path to basix Python sources (e.g. .../basix/python) + BASIX_SO_PATH – path to compiled _basixcpp.so (e.g. .../basix/build/python) +""" + +import sys +import os +import argparse +import basix + +# --------------------------------------------------------------------------- +# Basix path bootstrap – use environment or well-known local build. +# --------------------------------------------------------------------------- +_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) + + +# --------------------------------------------------------------------------- +# Cell-type table: (name, basix.CellType, topological_dimension) +# --------------------------------------------------------------------------- +CELL_TYPES = [ + ("interval", basix.CellType.interval, 1), + ("triangle", basix.CellType.triangle, 2), + ("quadrilateral", basix.CellType.quadrilateral, 2), + ("tetrahedron", basix.CellType.tetrahedron, 3), + ("hexahedron", basix.CellType.hexahedron, 3), + ("prism", basix.CellType.prism, 3), + ("pyramid", basix.CellType.pyramid, 3), +] + +# --------------------------------------------------------------------------- +# Code-generation helpers +# --------------------------------------------------------------------------- + +_HEADER_TMPL = """\ +// AUTO-GENERATED by gen_quadrature_tables.py — do not edit. +// Basix {version} quadrature rules for '{cell_type}', orders 1..{max_order}. +// Regenerate: python tablegen/scripts/gen_quadrature_tables.py {max_order} +// +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace cutcells::quadrature::generated +{{ +""" + +_FOOTER = "} // namespace cutcells::quadrature::generated\n" + + +def _fmt_doubles(arr, indent=4): + """Format a flat numpy array as a C++ double initialiser list (no braces).""" + vals = arr.ravel().tolist() + pad = " " * indent + cols = 4 # values per source line + lines = [] + for i in range(0, len(vals), cols): + chunk = vals[i : i + cols] + lines.append(pad + ", ".join(f"{v:.18e}" for v in chunk)) + return ",\n".join(lines) + + +def _write_cell_header(cell_name, basix_ct, tdim, max_order, out_dir): + fname = os.path.join(out_dir, f"quadrature_tables_{cell_name}.h") + with open(fname, "w") as fh: + fh.write( + _HEADER_TMPL.format( + version=basix.__version__, + cell_type=cell_name, + max_order=max_order, + ) + ) + for order in range(1, max_order + 1): + # Use Gauss-Lobatto-Legendre (GLL) for 1D interval tables so + # endpoints are included; keep defaults for other cell types. + if basix_ct == basix.CellType.interval: + pts, wts = basix.make_quadrature( + basix_ct, order, rule=basix.QuadratureType.gll + ) + else: + pts, wts = basix.make_quadrature(basix_ct, order) + npts = pts.shape[0] + pts_flat = pts.ravel() + + fh.write(f"// ---- order {order}: {npts} point(s), tdim={tdim} ----\n") + fh.write( + f"inline constexpr int {cell_name}_o{order}_npts = {npts};\n" + ) + fh.write( + f"inline constexpr int {cell_name}_o{order}_tdim = {tdim};\n" + ) + fh.write( + f"inline constexpr double {cell_name}_o{order}_points" + f"[{npts * tdim}] = {{\n" + ) + fh.write(_fmt_doubles(pts_flat)) + fh.write("\n};\n") + fh.write( + f"inline constexpr double {cell_name}_o{order}_weights[{npts}] = {{\n" + ) + fh.write(_fmt_doubles(wts)) + fh.write("\n};\n\n") + + fh.write(_FOOTER) + print(f" {fname}") + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "max_order", + nargs="?", + type=int, + default=10, + help="Maximum quadrature order to generate (default: 10)", + ) + parser.add_argument( + "out_dir", + nargs="?", + default=os.path.join(_REPO_ROOT, "cpp", "src", "generated"), + help="Output directory (default: cpp/src/generated)", + ) + args = parser.parse_args() + + out_dir = os.path.abspath(args.out_dir) + os.makedirs(out_dir, exist_ok=True) + + print( + f"Generating Basix {basix.__version__} quadrature tables " + f"(orders 1..{args.max_order}) -> {out_dir}" + ) + for cell_name, basix_ct, tdim in CELL_TYPES: + _write_cell_header(cell_name, basix_ct, tdim, args.max_order, out_dir) + print("Done.") + + +if __name__ == "__main__": + main()