diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0aa4337..3d8892a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: curl -L -O https://tiker.net/ci-support-v0 . ci-support-v0 - build_py_project_in_conda_env install_and_run_flake8 "$(get_proj_name)" examples/*.py test/*.py benchmarks/*.py pytest: @@ -32,9 +31,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - name: "Main Script" run: | export CONDA_ENVIRONMENT=.test-conda-env-py3.yml @@ -44,6 +40,10 @@ jobs: . ci-support-v0 build_py_project_in_conda_env + pushd contrib/meshgen11_dealii + bash build.sh + popd + test_py_project pytest_mac: @@ -51,9 +51,6 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - name: "Main Script" run: | export LC_ALL=en_US.UTF-8 @@ -66,6 +63,10 @@ jobs: . ci-support-v0 build_py_project_in_conda_env + pushd contrib/meshgen11_dealii + bash build.sh + popd + test_py_project docs: @@ -73,9 +74,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - name: "Main Script" run: | export CONDA_ENVIRONMENT=.test-conda-env-py3.yml @@ -84,6 +82,10 @@ jobs: . ci-support-v0 build_py_project_in_conda_env + pushd contrib/meshgen11_dealii + bash build.sh + popd + build_docs examples: @@ -91,16 +93,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - name: "Main Script" run: | export CONDA_ENVIRONMENT=.test-conda-env-py3.yml curl -L -O https://tiker.net/ci-support-v0 . ci-support-v0 + build_py_project_in_conda_env + pushd contrib/meshgen11_dealii + bash build.sh + popd + run_examples # vim: sw=2 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1f4bfac..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "contrib/meshgen11_dealii/pybind11"] - path = contrib/meshgen11_dealii/pybind11 - url = https://github.com/pybind/pybind11.git diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml index be59aaa..654d448 100644 --- a/.test-conda-env-py3-macos.yml +++ b/.test-conda-env-py3-macos.yml @@ -21,6 +21,7 @@ dependencies: - pluggy - pocl - py + - pybind11 - pyevtk - pyfmmlib - pyopencl @@ -28,7 +29,7 @@ dependencies: - pytest - pytest-cov - python-symengine - - python>=3.8 + - python=3 - pyvkfft - scipy - symengine diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index 7004f94..21a39f1 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -20,6 +20,7 @@ dependencies: - pluggy - pocl - py + - pybind11 - pyevtk - pyfmmlib - pyopencl @@ -27,7 +28,7 @@ dependencies: - pytest - pytest-cov - python-symengine - - python>=3.8 + - python=3 - pyvkfft - scipy - symengine diff --git a/contrib/meshgen11_dealii/CMakeLists.txt b/contrib/meshgen11_dealii/CMakeLists.txt index 3073e50..b27095e 100644 --- a/contrib/meshgen11_dealii/CMakeLists.txt +++ b/contrib/meshgen11_dealii/CMakeLists.txt @@ -8,10 +8,10 @@ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ## copies of the Software, and to permit persons to whom the Software is ## furnished to do so, subject to the following conditions: -## +## ## The above copyright notice and this permission notice shall be included in ## all copies or substantial portions of the Software. -## +## ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,65 +22,36 @@ ## ## --------------------------------------------------------------------- -CMAKE_MINIMUM_REQUIRED(VERSION 3.5.0) -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -PROJECT(meshgen11_dealii) +cmake_minimum_required(VERSION 3.15.0) +project(meshgen11_dealii LANGUAGES CXX) -IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "DEBUG") - #SET(CMAKE_BUILD_TYPE "RELEASE") - #SET(CMAKE_BUILD_TYPE "RELWITHDEBINFO") - #SET(CMAKE_BUILD_TYPE "MINSIZEREL") -ENDIF() +set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ version selection") -# set(DEAL_II_DIR "/home/xywei/apps" CACHE STRING "Deal.II dir") -# deal.II >= 8.4.1 -FIND_PACKAGE(deal.II REQUIRED - HINTS ${DEAL_II_DIR} ../ ../../ $ENV{DEAL_II_DIR} $ENV{CONDA_PREFIX} - ) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() -DEAL_II_INITIALIZE_CACHED_VARIABLES() +# deal.II +find_package(deal.II REQUIRED + HINTS ${DEAL_II_DIR} $ENV{DEAL_II_DIR} $ENV{CONDA_PREFIX}) +deal_ii_initialize_cached_variables() -find_package( PythonInterp 3.8 EXACT ) -find_package( PythonLibs 3.8 EXACT ) +# Python +find_package(Python REQUIRED COMPONENTS Interpreter Development) +find_package(pybind11 REQUIRED CONFIG) -add_subdirectory(pybind11) pybind11_add_module(meshgen11 meshgen.cpp) -set_target_properties(meshgen11 PROPERTIES PREFIX "") -set_target_properties(meshgen11 PROPERTIES OUTPUT_NAME "meshgen") - -if(FALSE) - # This script does not work on current Debian testing - DEAL_II_SETUP_TARGET(meshgen11) -else() - SET(_build "RELEASE") - STRING(TOLOWER "${CMAKE_BUILD_TYPE}" _cmake_build_type) - IF("${_cmake_build_type}" MATCHES "debug") - SET(_build "DEBUG") - ENDIF() - SET_PROPERTY(TARGET meshgen11 APPEND PROPERTY - INCLUDE_DIRECTORIES "${DEAL_II_INCLUDE_DIRS}" - ) - SET_PROPERTY(TARGET meshgen11 APPEND PROPERTY - INCLUDE_DIRECTORIES "/usr/include/" - ) - SET_PROPERTY(TARGET meshgen11 APPEND_STRING PROPERTY - COMPILE_FLAGS "${DEAL_II_CXX_FLAGS} ${DEAL_II_CXX_FLAGS_${_build}}" - ) - SET_PROPERTY(TARGET meshgen11 APPEND_STRING PROPERTY - LINK_FLAGS " ${DEAL_II_LINKER_FLAGS} ${DEAL_II_LINKER_FLAGS_${_build}} $ENV{DEAL_II_LINKER_FLAGS}" - ) - SET_PROPERTY(TARGET meshgen11 APPEND PROPERTY - COMPILE_DEFINITIONS "${DEAL_II_USER_DEFINITIONS};${DEAL_II_USER_DEFINITIONS_${_build}}" - ) - GET_PROPERTY(_type TARGET meshgen11 PROPERTY TYPE) - IF(NOT "${_type}" STREQUAL "OBJECT_LIBRARY") - TARGET_LINK_LIBRARIES(meshgen11 PRIVATE ${DEAL_II_TARGET_${_build}}) - ENDIF() -endif() - -ADD_CUSTOM_COMMAND(TARGET meshgen11 POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "meshgen.*.so" - "meshgen.so" - COMMENT "Copied meshgen lib to meshgen.so") +set_target_properties(meshgen11 PROPERTIES + OUTPUT_NAME "meshgen" + LINK_FLAGS ${DEAL_II_LINKER_FLAGS} ${DEAL_II_LINKER_FLAGS_RELEASE} + COMPILE_DEFINITIONS ${DEAL_II_USER_DEFINITIONS} + INTERPROCEDURAL_OPTIMIZATION ON + VISIBILITY_INLINES_HIDDEN ON) +target_include_directories(meshgen11 PRIVATE ${DEAL_II_INCLUDE_DIRS}) +target_link_libraries(meshgen11 PRIVATE ${DEAL_II_TARGET_RELEASE}) + +add_custom_command(TARGET meshgen11 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "meshgen.*.so" + "meshgen.so" + COMMENT "Copied meshgen lib to meshgen.so") diff --git a/contrib/meshgen11_dealii/README.md b/contrib/meshgen11_dealii/README.md index 1de97ae..c3e7ec7 100644 --- a/contrib/meshgen11_dealii/README.md +++ b/contrib/meshgen11_dealii/README.md @@ -13,9 +13,7 @@ To build `meshgen11`, you need: ## Usage The script `build.sh` also copies the built library to voluemential path so that you can use - -``` +```python import voluemential.meshgen ``` - directly. diff --git a/contrib/meshgen11_dealii/build.sh b/contrib/meshgen11_dealii/build.sh index c2e72f6..5cf490f 100755 --- a/contrib/meshgen11_dealii/build.sh +++ b/contrib/meshgen11_dealii/build.sh @@ -9,10 +9,10 @@ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ## copies of the Software, and to permit persons to whom the Software is ## furnished to do so, subject to the following conditions: -## +## ## The above copyright notice and this permission notice shall be included in ## all copies or substantial portions of the Software. -## +## ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -25,22 +25,20 @@ set -e -mkdir -p build -cd build -cmake .. -make -cd .. +cmake -Bbuild -DCMAKE_BUILD_TYPE=Release +cmake --build build --verbose -echo Module built: -echo ${PWD}/build/meshgen.so +echo 'Module built:' +echo "${PWD}/build/meshgen.so" +outfile=$(realpath ../../volumential/meshgen_dealii.so) if [ -f build/meshgen.so ]; then - cp build/meshgen.so ../../volumential/meshgen_dealii.so + cp build/meshgen.so "${outfile}" elif [ -f build/meshgen.dylib ]; then - cp build/meshgen.dylib ../../volumential/meshgen_dealii.so + cp build/meshgen.dylib "${outfile}" else echo "Something went wrong. Build failed." exit 1 fi -echo Library copied to volumential path. +echo "Library copied to '${outfile}'." diff --git a/contrib/meshgen11_dealii/meshgen.cpp b/contrib/meshgen11_dealii/meshgen.cpp index 6f3d15d..02aa91a 100644 --- a/contrib/meshgen11_dealii/meshgen.cpp +++ b/contrib/meshgen11_dealii/meshgen.cpp @@ -49,7 +49,7 @@ constexpr double b = 1; const d::UpdateFlags update_flags = d::update_quadrature_points | d::update_JxW_values; -void greet() { std::cout << "Hello from meshgen11_dealii." << std::endl; } +void greet() { std::cout << "Hello from mesh generation via deal.II." << std::endl; } // {{{ utils @@ -605,6 +605,7 @@ py::tuple make_uniform_cubic_grid(int q, int level, int dim, return make_uniform_cubic_grid_details<3>(q, level); } else { std::cout << "Dimension must be 1,2 or 3." << std::endl; + return py::tuple(); } } diff --git a/contrib/meshgen11_dealii/pybind11 b/contrib/meshgen11_dealii/pybind11 deleted file mode 160000 index f5f6618..0000000 --- a/contrib/meshgen11_dealii/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f5f66189620b7575f3124c66e3b3c8d0d03caf25 diff --git a/examples/laplace2d.py b/examples/laplace2d.py index fc0ab91..2e9db31 100644 --- a/examples/laplace2d.py +++ b/examples/laplace2d.py @@ -42,6 +42,7 @@ import pymbolic as pmbl import pyopencl as cl +import pyopencl.array from volumential.tools import ScalarFieldExpressionEvaluation as Eval diff --git a/examples/laplace3d.py b/examples/laplace3d.py index 5fdce13..e7b237c 100644 --- a/examples/laplace3d.py +++ b/examples/laplace3d.py @@ -32,6 +32,7 @@ import pymbolic as pmbl import pymbolic.functions import pyopencl as cl +import pyopencl.array from volumential.tools import ScalarFieldExpressionEvaluation as Eval diff --git a/volumential/meshgen.py b/volumential/meshgen.py index 9e0bf36..3a04112 100644 --- a/volumential/meshgen.py +++ b/volumential/meshgen.py @@ -21,14 +21,18 @@ """ __doc__ = """ -Mesh generation +Mesh generation. -.. autoclass:: MeshGenBase +.. autoclass:: MeshGen1D :members: .. autoclass:: MeshGen2D :members: .. autoclass:: MeshGen3D :members: + +.. autofunction:: greet +.. autofunction:: make_uniform_cubic_grid +.. autofunction:: build_geometry_info """ import logging @@ -39,265 +43,57 @@ from pytools.obj_array import make_obj_array -logger = logging.getLogger(__name__) - -provider = None - -# {{{ meshgen Python provider - - -class MeshGenBase: - """Base class for Meshgen via BoxTree. - The interface is similar to the Meshgen via Deal.II, except that - the arguments a and b can also be of higher dimensions to allow - added flexibility on choosing the bounding box. - - This base class cannot be used directly since the boxtree is not - built in the constructor until dimension_specific_setup() is - provided. - - In addition to the common capabilities of the deal.II implementation, - this class also implements native CL getters like get_q_points_dev() - that play well with the other libraries like boxtree. - """ - - def __init__(self, degree, nlevels, a=-1, b=1, queue=None): - assert degree > 0 - assert nlevels > 0 - self.degree = degree - self.quadrature_formula = LegendreGaussQuadrature(degree - 1) - self.nlevels = nlevels - - self.bound_a = np.array([a]).flatten() - self.bound_b = np.array([b]).flatten() - assert len(self.bound_a) == len(self.bound_b) - assert np.all(self.bound_a < self.bound_b) - - self.dim = len(self.bound_a) - self.root_vertex = self.bound_a - self.root_extent = np.max(self.bound_b - self.bound_a) - - if queue is None: - ctx = cl.create_some_context() - self.queue = cl.CommandQueue(ctx) - else: - self.queue = queue - - # plug in dimension-specific details - self.dimension_specific_setup() - self.n_q_points = self.degree ** self.dim - - self.boxtree = BoxTree() - self.boxtree.generate_uniform_boxtree( - self.queue, - nlevels=self.nlevels, - root_extent=self.root_extent, - root_vertex=self.root_vertex, - ) - self.quadrature = QuadratureOnBoxTree( - self.boxtree, self.quadrature_formula - ) - - def dimension_specific_setup(self): - pass - - def get_q_points_dev(self): - return self.quadrature.get_q_points(self.queue) - - def get_q_points(self): - q_points_dev = self.get_q_points_dev() - n_all_q_points = len(q_points_dev[0]) - q_points = np.zeros((n_all_q_points, self.dim)) - for d in range(self.dim): - q_points[:, d] = q_points_dev[d].get(self.queue) - return q_points - - def get_q_weights_dev(self): - return self.quadrature.get_q_weights(self.queue) - - def get_q_weights(self): - q_weights_dev = self.get_q_weights_dev() - return q_weights_dev.get(self.queue) - - def get_cell_measures_dev(self): - return self.quadrature.get_cell_measures(self.queue) - - def get_cell_measures(self): - cell_measures_dev = self.get_cell_measures_dev() - return cell_measures_dev.get(self.queue) - - def get_cell_centers_dev(self): - return self.quadrature.get_cell_centers(self.queue) - - def get_cell_centers(self): - cell_centers_dev = self.get_cell_centers_dev() - n_active_cells = self.n_active_cells() - cell_centers = np.zeros((n_active_cells, self.dim)) - for d in range(self.dim): - cell_centers[:, d] = cell_centers_dev[d].get(self.queue) - return cell_centers - - def n_cells(self): - """Note that this value can be larger than the actual number - of cells used in the boxtree. It mainly serves as the bound for - iterators on cells. - """ - return self.boxtree.nboxes - - def n_active_cells(self): - return self.boxtree.n_active_boxes - - def update_mesh( - self, criteria, top_fraction_of_cells, bottom_fraction_of_cells - ): - # TODO - raise NotImplementedError - - def print_info(self, logging_func=logger.info): - logging_func("Number of cells: " + str(self.n_cells())) - logging_func("Number of active cells: " + str(self.n_active_cells())) - logging_func("Number of quad points per cell: " - + str(self.n_q_points)) - - def generate_gmsh(self, filename): - """ - # TODO - Write the active boxes as a gmsh file. - The file format specifications can be found at: - http://gmsh.info/doc/texinfo/gmsh.html#MSH-ASCII-file-format - """ - raise NotImplementedError - - -# }}} End meshgen Python provider - try: - logger.info("Trying to find a mesh generator..") - import volumential.meshgen_dealii # noqa: F401 - - provider = "meshgen_dealii" - -except ImportError as e: - logger.debug(repr(e)) - logger.warning("Meshgen via Deal.II is not present or unusable.") + from volumential.meshgen_dealii import MeshGen1D, MeshGen2D, MeshGen3D, greet + PROVIDER = "meshgen_dealii" + def make_uniform_cubic_grid(degree, level, dim, queue=None): + from volumential.meshgen_dealii import make_uniform_cubic_grid as _mucg + return _mucg(degree, level, dim) +except ImportError: try: - logger.info("Trying out BoxTree.TreeInteractiveBuild interface.") - import boxtree.tree_interactive_build # noqa: F401 - from modepy import LegendreGaussQuadrature - - provider = "meshgen_boxtree" - - except ImportError as ee: - logger.debug(repr(ee)) - logger.warning("Meshgen via BoxTree is not present or unusable.") - raise RuntimeError("Cannot find a usable Meshgen implementation.") - - else: - # {{{ Meshgen via BoxTree - logger.info("Using Meshgen via BoxTree interface.") - from boxtree.tree_interactive_build import BoxTree, QuadratureOnBoxTree - - def greet(): - return "Hello from Meshgen via BoxTree!" - - def make_uniform_cubic_grid(degree, nlevels=1, dim=2, queue=None, **kwargs): - """Uniform cubic grid in [-1,1]^dim. - This function provides backward compatibility with meshgen_dealii. - """ - if queue is None: - ctx = cl.create_some_context() - queue = cl.CommandQueue(ctx) + from volumential.meshgen_boxtree import ( + MeshGen1D, MeshGen2D, MeshGen3D, greet) + PROVIDER = "meshgen_boxtree" + except ImportError: + raise RuntimeError( + "Cannot find a usable mesh generation implementation. " + "Install the deal.II implementation or the boxtree " + "implementation to use mesh generation.") - # For meshgen_dealii compatibility - if "level" in kwargs: - nlevels = kwargs["level"] - tree = BoxTree() - tree.generate_uniform_boxtree( - queue, nlevels=nlevels, root_extent=2, root_vertex=np.zeros(dim) - 1 - ) - quad_rule = LegendreGaussQuadrature(degree - 1) - quad = QuadratureOnBoxTree(tree, quad_rule) - q_weights = quad.get_q_weights(queue).get(queue) - q_points_dev = quad.get_q_points(queue) - n_all_q_points = len(q_weights) - q_points = np.zeros((n_all_q_points, dim)) - for d in range(dim): - q_points[:, d] = q_points_dev[d].get(queue) +# FIXME: lowercase variable should be deprecated and removed +provider = PROVIDER - # Adding a placeholder for deprecated point radii - return (q_points, q_weights, None) +__all__ = ( + "provider", "PROVIDER", + "MeshGen1D", "MeshGen2D", "MeshGen3D", + "make_uniform_cubic_grid", + "greet", - class MeshGen1D(MeshGenBase): - """Meshgen in 1D - """ + "build_geometry_info", +) - def dimension_specific_setup(self): - assert self.dim == 1 - - class MeshGen2D(MeshGenBase): - """Meshgen in 2D - """ - - def dimension_specific_setup(self): - if self.dim == 1: - # allow passing scalar values of a and b to the constructor - self.dim = 2 - self.root_vertex = np.zeros(self.dim) + self.bound_a - else: - assert self.dim == 2 - - class MeshGen3D(MeshGenBase): - """Meshgen in 3D - """ - - def dimension_specific_setup(self): - if self.dim == 1: - # allow passing scalar values of a and b to the constructor - self.dim = 3 - self.root_vertex = np.zeros(self.dim) + self.bound_a - else: - assert self.dim == 3 - - # }}} End Meshgen via BoxTree - -else: - # noexcept on importing meshgen_dealii - logger.info("Using Meshgen via Deal.II interface.") - from volumential.meshgen_dealii import MeshGen2D, MeshGen3D, greet # noqa: F401 - - def make_uniform_cubic_grid(degree, level, dim, queue=None): - from volumential.meshgen_dealii import make_uniform_cubic_grid as _mucg - return _mucg(degree, level, dim) +logger = logging.getLogger(__name__) # {{{ mesh utils def build_geometry_info(ctx, queue, dim, q_order, mesh, - bbox=None, a=None, b=None): - """Build tree, traversal and other geo info for FMM computation, - given the box mesh over/encompassing the domain. + bbox=None, a=None, b=None): + """Build tree, traversal and other geometric information for FMM computation. The bouding box can be specified in one of two ways: 1. via scalars a, b, dim-homogeneous ([a, b]^dim) 2. via bbox (e.g. np.array([[a1, b1], [a2, b2], [a3, b3]])) """ - if dim == 1: - if not isinstance(mesh, MeshGen1D): - raise ValueError() + if dim != mesh.dim: + raise ValueError( + f"Got a mesh of dimension {mesh.dim}, but expected dimension {dim}") - if dim == 2: - if not isinstance(mesh, MeshGen2D): - raise ValueError() - - elif dim == 3: - if not isinstance(mesh, MeshGen3D): - raise ValueError() - - else: - raise ValueError("only supports 1 <= dim <= 3") + if not 1 <= dim <= 3: + raise ValueError(f"Unsupported dimension {dim}: 1 <= dim <= 3") q_points = mesh.get_q_points() q_weights = mesh.get_q_weights() @@ -331,6 +127,4 @@ def build_geometry_info(ctx, queue, dim, q_order, mesh, return q_points, q_weights, tree, trav -# }}} End mesh utils - -# vim: ft=pyopencl +# }}} diff --git a/volumential/meshgen_boxtree.py b/volumential/meshgen_boxtree.py new file mode 100644 index 0000000..15630d5 --- /dev/null +++ b/volumential/meshgen_boxtree.py @@ -0,0 +1,221 @@ +__copyright__ = "Copyright (C) 2018 Xiaoyu Wei" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +import logging + +import numpy as np + +import pyopencl as cl +from boxtree.tree_interactive_build import BoxTree, QuadratureOnBoxTree +from modepy import LegendreGaussQuadrature + + +logger = logging.getLogger(__name__) + + +def greet(): + return "Hello from mesh generation via boxtree!" + + +class MeshGenBase: + """Base class for Meshgen via BoxTree. + The interface is similar to the Meshgen via Deal.II, except that + the arguments a and b can also be of higher dimensions to allow + added flexibility on choosing the bounding box. + + This base class cannot be used directly since the boxtree is not + built in the constructor until dimension_specific_setup() is + provided. + + In addition to the common capabilities of the deal.II implementation, + this class also implements native CL getters like get_q_points_dev() + that play well with the other libraries like boxtree. + """ + + def __init__(self, degree, nlevels, a=-1, b=1, queue=None): + assert degree > 0 + assert nlevels > 0 + self.degree = degree + self.quadrature_formula = LegendreGaussQuadrature(degree - 1) + self.nlevels = nlevels + + self.bound_a = np.array([a]).flatten() + self.bound_b = np.array([b]).flatten() + assert len(self.bound_a) == len(self.bound_b) + assert np.all(self.bound_a < self.bound_b) + + self.dim = len(self.bound_a) + self.root_vertex = self.bound_a + self.root_extent = np.max(self.bound_b - self.bound_a) + + if queue is None: + ctx = cl.create_some_context() + self.queue = cl.CommandQueue(ctx) + else: + self.queue = queue + + # plug in dimension-specific details + self.dimension_specific_setup() + self.n_q_points = self.degree ** self.dim + + self.boxtree = BoxTree() + self.boxtree.generate_uniform_boxtree( + self.queue, + nlevels=self.nlevels, + root_extent=self.root_extent, + root_vertex=self.root_vertex, + ) + self.quadrature = QuadratureOnBoxTree( + self.boxtree, self.quadrature_formula + ) + + def dimension_specific_setup(self): + pass + + def get_q_points_dev(self): + return self.quadrature.get_q_points(self.queue) + + def get_q_points(self): + q_points_dev = self.get_q_points_dev() + n_all_q_points = len(q_points_dev[0]) + q_points = np.zeros((n_all_q_points, self.dim)) + for d in range(self.dim): + q_points[:, d] = q_points_dev[d].get(self.queue) + return q_points + + def get_q_weights_dev(self): + return self.quadrature.get_q_weights(self.queue) + + def get_q_weights(self): + q_weights_dev = self.get_q_weights_dev() + return q_weights_dev.get(self.queue) + + def get_cell_measures_dev(self): + return self.quadrature.get_cell_measures(self.queue) + + def get_cell_measures(self): + cell_measures_dev = self.get_cell_measures_dev() + return cell_measures_dev.get(self.queue) + + def get_cell_centers_dev(self): + return self.quadrature.get_cell_centers(self.queue) + + def get_cell_centers(self): + cell_centers_dev = self.get_cell_centers_dev() + n_active_cells = self.n_active_cells() + cell_centers = np.zeros((n_active_cells, self.dim)) + for d in range(self.dim): + cell_centers[:, d] = cell_centers_dev[d].get(self.queue) + return cell_centers + + def n_cells(self): + """Note that this value can be larger than the actual number + of cells used in the boxtree. It mainly serves as the bound for + iterators on cells. + """ + return self.boxtree.nboxes + + def n_active_cells(self): + return self.boxtree.n_active_boxes + + def update_mesh( + self, criteria, top_fraction_of_cells, bottom_fraction_of_cells + ): + # TODO + raise NotImplementedError + + def print_info(self, logging_func=logger.info): + logging_func("Number of cells: " + str(self.n_cells())) + logging_func("Number of active cells: " + str(self.n_active_cells())) + logging_func("Number of quad points per cell: " + + str(self.n_q_points)) + + def generate_gmsh(self, filename): + """ + # TODO + Write the active boxes as a gmsh file. + The file format specifications can be found at: + http://gmsh.info/doc/texinfo/gmsh.html#MSH-ASCII-file-format + """ + raise NotImplementedError + + +class MeshGen1D(MeshGenBase): + """Meshgen in 1D + """ + + def dimension_specific_setup(self): + assert self.dim == 1 + + +class MeshGen2D(MeshGenBase): + """Meshgen in 2D + """ + + def dimension_specific_setup(self): + if self.dim == 1: + # allow passing scalar values of a and b to the constructor + self.dim = 2 + self.root_vertex = np.zeros(self.dim) + self.bound_a + else: + assert self.dim == 2 + + +class MeshGen3D(MeshGenBase): + """Meshgen in 3D + """ + + def dimension_specific_setup(self): + if self.dim == 1: + # allow passing scalar values of a and b to the constructor + self.dim = 3 + self.root_vertex = np.zeros(self.dim) + self.bound_a + else: + assert self.dim == 3 + + +def make_uniform_cubic_grid(degree, nlevels=1, dim=2, queue=None, **kwargs): + """Uniform cubic grid in [-1,1]^dim. + This function provides backward compatibility with meshgen_dealii. + """ + if queue is None: + ctx = cl.create_some_context() + queue = cl.CommandQueue(ctx) + + # For meshgen_dealii compatibility + if "level" in kwargs: + nlevels = kwargs["level"] + + tree = BoxTree() + tree.generate_uniform_boxtree( + queue, nlevels=nlevels, root_extent=2, root_vertex=np.zeros(dim) - 1 + ) + quad_rule = LegendreGaussQuadrature(degree - 1) + quad = QuadratureOnBoxTree(tree, quad_rule) + q_weights = quad.get_q_weights(queue).get(queue) + q_points_dev = quad.get_q_points(queue) + n_all_q_points = len(q_weights) + q_points = np.zeros((n_all_q_points, dim)) + for d in range(dim): + q_points[:, d] = q_points_dev[d].get(queue) + + # Adding a placeholder for deprecated point radii + return (q_points, q_weights, None) diff --git a/volumential/volume_fmm.py b/volumential/volume_fmm.py index 6496ebf..517edd5 100644 --- a/volumential/volume_fmm.py +++ b/volumential/volume_fmm.py @@ -30,7 +30,7 @@ import numpy as np import pyopencl as cl -from boxtree.fmm import TimingRecorder +from boxtree.timing import TimingRecorder from pytools.obj_array import make_obj_array from volumential.expansion_wrangler_fpnd import (