From fb66bdf69d0bec2be1f1fe1d302dba4f29d0b361 Mon Sep 17 00:00:00 2001 From: Markus Holzer <markus.holzer@fau.de> Date: Thu, 17 Dec 2020 22:01:29 +0100 Subject: [PATCH] load pybind11 after finding python libraries also clean up some old leftovers nearby --- .gitlab-ci.yml | 262 +- .gitmodules | 3 + CHANGELOG.md | 5 + CMakeLists.txt | 127 +- apps/CMakeLists.txt | 2 - apps/benchmarks/CMakeLists.txt | 2 +- .../FieldCommunication/CMakeLists.txt | 2 +- .../FieldCommunication/FieldCommunication.cpp | 4 +- .../PhaseFieldAllenCahn/CMakeLists.txt | 2 + .../InitializerFunctions.cpp | 2 - .../InitializerFunctions.h | 3 - .../benchmark_multiphase.cpp | 2 - .../PhaseFieldAllenCahn/profiling.py | 2 - apps/benchmarks/UniformGridGPU/CMakeLists.txt | 4 +- .../UniformGridGPU_Communication.h | 2 +- .../UniformGridGenerated/CMakeLists.txt | 3 +- apps/pythonmodule/CMakeLists.txt | 43 +- apps/pythonmodule/PythonModule.cpp | 149 +- .../PythonModuleWithLbmModule.cpp | 234 -- apps/pythonmodule/setup.py | 34 +- .../PhaseFieldAllenCahn/CPU/PythonExports.cpp | 58 +- .../PhaseFieldAllenCahn/CPU/multiphase.cpp | 6 +- .../CPU/multiphase_RTI_3D.py | 14 +- .../CPU/multiphase_rising_bubble.py | 10 +- .../PhaseFieldAllenCahn/GPU/PythonExports.cpp | 58 +- .../PhaseFieldAllenCahn/GPU/multiphase.cpp | 6 +- .../GPU/multiphase_RTI_3D.py | 12 +- .../GPU/multiphase_rising_bubble.py | 10 +- cmake/FindBoost.cmake | 2213 ----------------- doc/setup.dox | 40 +- extern/pybind11 | 1 + python/pystencils_walberla/boundary.py | 2 - python/waLBerla/__init__.py | 17 +- python/waLBerla/callbacks.py | 46 +- python/waLBerla/core_extension.py | 56 +- python/waLBerla/cuda_extension.py | 20 +- python/waLBerla/field_extension.py | 87 +- python/waLBerla/geometry_setup.py | 148 -- python/waLBerla/plot.py | 171 +- python/waLBerla/timeloop_extension.py | 69 - python/waLBerla/tools/sqlitedb/insert.py | 66 +- python/waLBerla/tools/sqlitedb/merge.py | 20 +- python/waLBerla_docs/conf.py | 4 +- python/waLBerla_docs/index.rst | 41 +- .../Tutorial 01 - Basic data structures.ipynb | 310 ++- .../ipython-tutorials/Tutorial 02 - LBM.ipynb | 254 -- .../Tutorial 03 - LBM with extensions.ipynb | 272 -- python/waLBerla_docs/modules/blockforest.rst | 32 +- python/waLBerla_docs/modules/core.rst | 17 +- python/waLBerla_docs/modules/field.rst | 127 +- python/waLBerla_docs/modules/geometry.rst | 67 - python/waLBerla_docs/modules/lbm.rst | 261 -- python/waLBerla_tests/test_blockforest.py | 69 +- python/waLBerla_tests/test_core.py | 4 +- python/waLBerla_tests/test_cuda_comm.py | 25 - python/waLBerla_tests/test_field.py | 57 +- python/waLBerla_tests/test_simpleLBM.py | 198 -- .../tools/test_lbm_unitconversion.py | 8 +- python/waLBerla_tests/wing.png | Bin 1146 -> 0 bytes src/blockforest/CMakeLists.txt | 2 +- .../communication/UniformBufferedScheme.h | 7 +- .../communication/UniformDirectScheme.h | 7 +- .../python/CommunicationExport.impl.h | 235 -- src/blockforest/python/Exports.cpp | 327 --- src/blockforest/python/Exports.h | 51 - src/boundary/python/Exports.h | 39 - src/boundary/python/Exports.impl.h | 232 -- src/communication/UniformMPIDatatypeInfo.h | 8 + src/core/WeakPtrWrapper.h | 100 - src/cuda/CMakeLists.txt | 2 +- src/cuda/communication/UniformGPUScheme.h | 5 +- .../communication/UniformGPUScheme.impl.h | 2 +- src/cuda/python/Exports.impl.h | 407 --- src/field/Traits.h | 61 - src/field/python/CommunicationExport.impl.h | 260 -- src/field/python/FieldExport.h | 65 - src/field/python/FieldExport.impl.h | 1396 ----------- src/field/python/GatherExport.h | 38 - src/field/python/GatherExport.impl.h | 109 - src/geometry/python/Exports.cpp | 103 - src/geometry/python/Exports.h | 38 - src/lbm/python/ExportBasic.cpp | 244 -- src/lbm/python/ExportBasic.h | 47 - src/lbm/python/ExportBasic.impl.h | 520 ---- src/lbm/python/ExportBoundary.h | 39 - src/lbm/python/ExportBoundary.impl.h | 279 --- src/lbm/python/ExportSweeps.h | 39 - src/lbm/python/ExportSweeps.impl.h | 260 -- src/lbm/python/Exports.h | 49 - src/mesh/python/Exports.h | 41 - src/mesh/python/Exports.impl.h | 186 -- src/postprocessing/python/Exports.h | 41 - src/postprocessing/python/Exports.impl.h | 141 -- src/python_coupling/CMakeLists.txt | 8 +- src/python_coupling/CreateConfig.cpp | 314 +-- src/python_coupling/CreateConfig.h | 3 +- src/python_coupling/DictWrapper.h | 23 +- src/python_coupling/DictWrapper.impl.h | 48 +- src/python_coupling/Manager.cpp | 85 +- src/python_coupling/Manager.h | 41 +- src/python_coupling/PythonCallback.cpp | 90 +- src/python_coupling/PythonCallback.h | 5 +- src/python_coupling/PythonWrapper.h | 5 +- src/python_coupling/Shell.cpp | 247 -- src/python_coupling/Shell.h | 63 - src/python_coupling/TimeloopIntercept.cpp | 136 - src/python_coupling/TimeloopIntercept.h | 60 - src/python_coupling/all.h | 32 - .../basic_exports/BasicExports.cpp | 1238 --------- .../basic_exports/MPIExport.cpp | 266 -- src/python_coupling/export/BasicExport.cpp | 728 ++++++ .../BasicExports.h => export/BasicExport.h} | 5 +- .../export/BlockForestCommunicationExport.h} | 12 +- .../BlockForestCommunicationExport.impl.h | 242 ++ .../export/BlockForestExport.cpp | 340 +++ .../export/BlockForestExport.h | 69 + .../export/CUDAExport.h} | 13 +- src/python_coupling/export/CUDAExport.impl.h | 394 +++ .../export/FieldCommunicationExport.h} | 9 +- .../export/FieldCommunicationExport.impl.h | 249 ++ src/python_coupling/export/FieldExport.impl.h | 664 +++++ .../export/FieldExports.h} | 43 +- .../export/GatherExport.impl.h | 141 ++ src/python_coupling/export/MPIExport.cpp | 278 +++ .../{basic_exports => export}/MPIExport.h | 3 +- .../export/VTKExport.cpp} | 69 +- .../export/VTKExport.h} | 13 +- .../helper/BlockStorageExportHelpers.cpp | 13 +- .../helper/BlockStorageExportHelpers.h | 11 +- .../helper/BoostPythonHelpers.h | 106 - src/python_coupling/helper/ConfigFromDict.cpp | 77 +- src/python_coupling/helper/ConfigFromDict.h | 5 +- .../helper/ExceptionHandling.h | 71 - src/python_coupling/helper/ModuleInit.cpp | 4 +- src/python_coupling/helper/ModuleInit.h | 1 + src/python_coupling/helper/ModuleScope.h | 56 - src/python_coupling/helper/MplHelpers.h | 36 +- src/python_coupling/helper/OwningIterator.h | 74 + src/python_coupling/helper/PybindHelper.h | 65 + .../helper/PythonIterableToStdVector.h | 10 +- src/python_coupling/helper/SharedPtrDeleter.h | 65 - .../helper/SliceToCellInterval.h | 247 +- src/timeloop/python/Exports.cpp | 89 - src/timeloop/python/Exports.h | 41 - src/vtk/CMakeLists.txt | 2 +- src/walberla.h | 1 - tests/core/CMakeLists.txt | 6 +- tests/pe/SyncEquivalence.cpp | 2 - tests/python_coupling/BasicDatatypeTest.py | 26 - tests/python_coupling/CMakeLists.txt | 11 +- tests/python_coupling/CallbackTest.cpp | 21 +- tests/python_coupling/CallbackTest.py | 10 +- .../python_coupling/ConfigFromPythonTest.cpp | 57 +- tests/python_coupling/ConfigFromPythonTest.py | 42 +- tests/python_coupling/FieldExportTest.cpp | 80 +- tests/python_coupling/FieldExportTest.py | 4 +- utilities/conda/walberla/bld.bat | 1 - utilities/conda/walberla/build.sh | 2 - 158 files changed, 4871 insertions(+), 13444 deletions(-) create mode 100644 .gitmodules delete mode 100644 apps/pythonmodule/PythonModuleWithLbmModule.cpp delete mode 100644 cmake/FindBoost.cmake create mode 160000 extern/pybind11 delete mode 100644 python/waLBerla/geometry_setup.py delete mode 100644 python/waLBerla/timeloop_extension.py delete mode 100644 python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 02 - LBM.ipynb delete mode 100644 python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 03 - LBM with extensions.ipynb delete mode 100644 python/waLBerla_docs/modules/geometry.rst delete mode 100644 python/waLBerla_docs/modules/lbm.rst delete mode 100644 python/waLBerla_tests/test_cuda_comm.py delete mode 100644 python/waLBerla_tests/test_simpleLBM.py delete mode 100644 python/waLBerla_tests/wing.png delete mode 100644 src/blockforest/python/CommunicationExport.impl.h delete mode 100644 src/blockforest/python/Exports.cpp delete mode 100644 src/blockforest/python/Exports.h delete mode 100644 src/boundary/python/Exports.h delete mode 100644 src/boundary/python/Exports.impl.h delete mode 100644 src/core/WeakPtrWrapper.h delete mode 100644 src/cuda/python/Exports.impl.h delete mode 100644 src/field/Traits.h delete mode 100644 src/field/python/CommunicationExport.impl.h delete mode 100644 src/field/python/FieldExport.h delete mode 100644 src/field/python/FieldExport.impl.h delete mode 100644 src/field/python/GatherExport.h delete mode 100644 src/field/python/GatherExport.impl.h delete mode 100644 src/geometry/python/Exports.cpp delete mode 100644 src/geometry/python/Exports.h delete mode 100644 src/lbm/python/ExportBasic.cpp delete mode 100644 src/lbm/python/ExportBasic.h delete mode 100644 src/lbm/python/ExportBasic.impl.h delete mode 100644 src/lbm/python/ExportBoundary.h delete mode 100644 src/lbm/python/ExportBoundary.impl.h delete mode 100644 src/lbm/python/ExportSweeps.h delete mode 100644 src/lbm/python/ExportSweeps.impl.h delete mode 100644 src/lbm/python/Exports.h delete mode 100644 src/mesh/python/Exports.h delete mode 100644 src/mesh/python/Exports.impl.h delete mode 100644 src/postprocessing/python/Exports.h delete mode 100644 src/postprocessing/python/Exports.impl.h delete mode 100644 src/python_coupling/Shell.cpp delete mode 100644 src/python_coupling/Shell.h delete mode 100644 src/python_coupling/TimeloopIntercept.cpp delete mode 100644 src/python_coupling/TimeloopIntercept.h delete mode 100644 src/python_coupling/all.h delete mode 100644 src/python_coupling/basic_exports/BasicExports.cpp delete mode 100644 src/python_coupling/basic_exports/MPIExport.cpp create mode 100644 src/python_coupling/export/BasicExport.cpp rename src/python_coupling/{basic_exports/BasicExports.h => export/BasicExport.h} (87%) rename src/{blockforest/python/CommunicationExport.h => python_coupling/export/BlockForestCommunicationExport.h} (78%) create mode 100644 src/python_coupling/export/BlockForestCommunicationExport.impl.h create mode 100644 src/python_coupling/export/BlockForestExport.cpp create mode 100644 src/python_coupling/export/BlockForestExport.h rename src/{cuda/python/Exports.h => python_coupling/export/CUDAExport.h} (81%) create mode 100644 src/python_coupling/export/CUDAExport.impl.h rename src/{field/python/CommunicationExport.h => python_coupling/export/FieldCommunicationExport.h} (85%) create mode 100644 src/python_coupling/export/FieldCommunicationExport.impl.h create mode 100644 src/python_coupling/export/FieldExport.impl.h rename src/{field/python/Exports.h => python_coupling/export/FieldExports.h} (63%) create mode 100644 src/python_coupling/export/GatherExport.impl.h create mode 100644 src/python_coupling/export/MPIExport.cpp rename src/python_coupling/{basic_exports => export}/MPIExport.h (93%) rename src/{vtk/python/Exports.cpp => python_coupling/export/VTKExport.cpp} (53%) rename src/{vtk/python/Exports.h => python_coupling/export/VTKExport.h} (65%) delete mode 100644 src/python_coupling/helper/BoostPythonHelpers.h delete mode 100644 src/python_coupling/helper/ExceptionHandling.h delete mode 100644 src/python_coupling/helper/ModuleScope.h create mode 100644 src/python_coupling/helper/OwningIterator.h create mode 100644 src/python_coupling/helper/PybindHelper.h delete mode 100644 src/python_coupling/helper/SharedPtrDeleter.h delete mode 100644 src/timeloop/python/Exports.cpp delete mode 100644 src/timeloop/python/Exports.h delete mode 100644 tests/python_coupling/BasicDatatypeTest.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ceef61aeb..6e209960e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,7 +51,7 @@ stages: -DWALBERLA_ENABLE_GUI=$WALBERLA_ENABLE_GUI -DWALBERLA_BUILD_WITH_CODEGEN=$WALBERLA_BUILD_WITH_CODEGEN -DWALBERLA_STL_BOUNDS_CHECKS=$WALBERLA_STL_BOUNDS_CHECKS - - cmake . -LAH + - cmake . -LA - make -j $NUM_BUILD_CORES -l $NUM_CORES - ctest -LE $CTEST_EXCLUDE_LABELS -C $CMAKE_BUILD_TYPE --output-on-failure -j $NUM_CORES tags: @@ -87,12 +87,14 @@ intel_18_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -115,10 +117,12 @@ intel_18_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -141,10 +145,12 @@ intel_18_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_ENABLE_GUI: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -163,6 +169,7 @@ intel_18_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" @@ -170,6 +177,7 @@ intel_18_serial_dbg: WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -188,11 +196,13 @@ intel_18_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -215,10 +225,12 @@ intel_18_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -259,12 +271,14 @@ intel_19_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -274,6 +288,7 @@ intel_19_serial: needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -286,10 +301,12 @@ intel_19_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -299,6 +316,7 @@ intel_19_mpionly: needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -311,14 +329,17 @@ intel_19_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -331,18 +352,21 @@ intel_19_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -355,16 +379,19 @@ intel_19_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -377,11 +404,14 @@ intel_19_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" tags: + - cuda - docker - intel @@ -389,7 +419,7 @@ intel_19_hybrid_dbg_sp: extends: .build_template image: i10git.cs.fau.de:5005/walberla/buildenvs/intel:19 variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" @@ -399,6 +429,7 @@ intel_19_hybrid_dbg_sp: needs: [] allow_failure: false tags: + - cuda - docker - intel @@ -411,12 +442,14 @@ gcc_7_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -438,10 +471,12 @@ gcc_7_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -463,9 +498,11 @@ gcc_7_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -483,6 +520,7 @@ gcc_7_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" @@ -491,6 +529,7 @@ gcc_7_serial_dbg: CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_ENABLE_GUI: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -508,11 +547,13 @@ gcc_7_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -534,10 +575,12 @@ gcc_7_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -576,12 +619,14 @@ gcc_8_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -591,6 +636,7 @@ gcc_8_serial: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_mpionly: @@ -602,10 +648,12 @@ gcc_8_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -615,6 +663,7 @@ gcc_8_mpionly: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_hybrid: @@ -626,9 +675,11 @@ gcc_8_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -638,6 +689,7 @@ gcc_8_hybrid: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_serial_dbg: @@ -649,13 +701,15 @@ gcc_8_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -665,6 +719,7 @@ gcc_8_serial_dbg: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_mpionly_dbg: @@ -676,11 +731,13 @@ gcc_8_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -690,6 +747,7 @@ gcc_8_mpionly_dbg: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_hybrid_dbg: @@ -701,10 +759,12 @@ gcc_8_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -714,13 +774,14 @@ gcc_8_hybrid_dbg: needs: [] allow_failure: false tags: + - cuda - docker gcc_8_hybrid_dbg_sp: extends: .build_template image: i10git.cs.fau.de:5005/walberla/buildenvs/gcc:8 variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" @@ -734,6 +795,7 @@ gcc_8_hybrid_dbg_sp: needs: [] allow_failure: false tags: + - cuda - docker gcc_9_serial: @@ -751,6 +813,7 @@ gcc_9_serial: WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -775,6 +838,7 @@ gcc_9_mpionly: WALBERLA_BUILD_WITH_CUDA: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -798,6 +862,7 @@ gcc_9_hybrid: variables: WALBERLA_BUILD_WITH_CUDA: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -825,6 +890,7 @@ gcc_9_serial_dbg: WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -850,6 +916,7 @@ gcc_9_mpionly_dbg: CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -874,6 +941,7 @@ gcc_9_hybrid_dbg: WALBERLA_BUILD_WITH_CUDA: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -920,6 +988,7 @@ gcc_10_serial: WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -944,6 +1013,7 @@ gcc_10_mpionly: WALBERLA_BUILD_WITH_CUDA: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -968,6 +1038,7 @@ gcc_10_hybrid: variables: WALBERLA_BUILD_WITH_CUDA: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" tags: - docker @@ -987,6 +1058,7 @@ gcc_10_serial_dbg: WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" tags: - docker @@ -1004,6 +1076,7 @@ gcc_10_mpionly_dbg: CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -1024,6 +1097,7 @@ gcc_10_hybrid_dbg: WALBERLA_BUILD_WITH_CUDA: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -1056,12 +1130,14 @@ clang_6.0_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1083,10 +1159,12 @@ clang_6.0_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1108,9 +1186,11 @@ clang_6.0_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1132,6 +1212,7 @@ clang_6.0_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" @@ -1139,6 +1220,7 @@ clang_6.0_serial_dbg: WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1160,11 +1242,13 @@ clang_6.0_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1186,10 +1270,12 @@ clang_6.0_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] @@ -1228,12 +1314,14 @@ clang_7.0_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1243,6 +1331,7 @@ clang_7.0_serial: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_mpionly: @@ -1254,10 +1343,12 @@ clang_7.0_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1267,6 +1358,7 @@ clang_7.0_mpionly: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_hybrid: @@ -1278,9 +1370,11 @@ clang_7.0_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1290,6 +1384,7 @@ clang_7.0_hybrid: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_serial_dbg: @@ -1301,13 +1396,15 @@ clang_7.0_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1317,6 +1414,7 @@ clang_7.0_serial_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_mpionly_dbg: @@ -1328,11 +1426,13 @@ clang_7.0_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1342,6 +1442,7 @@ clang_7.0_mpionly_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_hybrid_dbg: @@ -1353,10 +1454,12 @@ clang_7.0_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1366,13 +1469,14 @@ clang_7.0_hybrid_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_7.0_hybrid_dbg_sp: extends: .build_template image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:7.0 variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" @@ -1386,6 +1490,7 @@ clang_7.0_hybrid_dbg_sp: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_serial: @@ -1397,12 +1502,14 @@ clang_8.0_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1412,6 +1519,7 @@ clang_8.0_serial: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_mpionly: @@ -1423,10 +1531,12 @@ clang_8.0_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1436,6 +1546,7 @@ clang_8.0_mpionly: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_hybrid: @@ -1447,9 +1558,11 @@ clang_8.0_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1459,6 +1572,7 @@ clang_8.0_hybrid: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_serial_dbg: @@ -1470,13 +1584,15 @@ clang_8.0_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1486,6 +1602,7 @@ clang_8.0_serial_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_mpionly_dbg: @@ -1497,11 +1614,13 @@ clang_8.0_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1511,6 +1630,7 @@ clang_8.0_mpionly_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_hybrid_dbg: @@ -1522,10 +1642,12 @@ clang_8.0_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1535,13 +1657,14 @@ clang_8.0_hybrid_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_8.0_hybrid_dbg_sp: extends: .build_template image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:8.0 variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" @@ -1555,6 +1678,7 @@ clang_8.0_hybrid_dbg_sp: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_serial: @@ -1566,12 +1690,14 @@ clang_9.0_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1581,6 +1707,7 @@ clang_9.0_serial: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_mpionly: @@ -1592,10 +1719,12 @@ clang_9.0_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1605,6 +1734,7 @@ clang_9.0_mpionly: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_hybrid: @@ -1616,9 +1746,11 @@ clang_9.0_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1628,6 +1760,7 @@ clang_9.0_hybrid: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_serial_dbg: @@ -1639,13 +1772,15 @@ clang_9.0_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1655,6 +1790,7 @@ clang_9.0_serial_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_mpionly_dbg: @@ -1666,11 +1802,13 @@ clang_9.0_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1680,6 +1818,7 @@ clang_9.0_mpionly_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_hybrid_dbg: @@ -1691,10 +1830,12 @@ clang_9.0_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1704,13 +1845,14 @@ clang_9.0_hybrid_dbg: needs: [] allow_failure: false tags: + - cuda - docker clang_9.0_hybrid_dbg_sp: extends: .build_template image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:9.0 variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" @@ -1724,6 +1866,7 @@ clang_9.0_hybrid_dbg_sp: needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_serial: @@ -1735,12 +1878,14 @@ clang_10.0_serial: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1750,6 +1895,7 @@ clang_10.0_serial: needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_mpionly: @@ -1761,10 +1907,12 @@ clang_10.0_mpionly: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" only: variables: - $ENABLE_NIGHTLY_BUILDS @@ -1774,6 +1922,7 @@ clang_10.0_mpionly: needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_hybrid: @@ -1785,14 +1934,17 @@ clang_10.0_hybrid: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_serial_dbg: @@ -1804,18 +1956,21 @@ clang_10.0_serial_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" WALBERLA_BUILD_WITH_MPI: "OFF" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_mpionly_dbg: @@ -1827,12 +1982,15 @@ clang_10.0_mpionly_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_OPENMP: "OFF" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" tags: + - cuda - docker clang_10.0_hybrid_dbg: @@ -1844,15 +2002,18 @@ clang_10.0_hybrid_dbg: - python3 -m unittest discover pystencils_walberla/ - python3 -m unittest discover lbmpy_walberla/ - cd .. + - CC=gcc CXX=g++ pip3 install pycuda variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_BUILD_WITH_CODEGEN: "ON" + WALBERLA_BUILD_WITH_PYTHON: "ON" stage: merge_request when: manual needs: [] allow_failure: false tags: + - cuda - docker clang_10.0_hybrid_dbg_sp: @@ -1860,12 +2021,13 @@ clang_10.0_hybrid_dbg_sp: image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:10.0 stage: pretest variables: - WALBERLA_BUILD_WITH_CUDA: "OFF" + WALBERLA_BUILD_WITH_CUDA: "ON" CMAKE_BUILD_TYPE: "DebugOptimized" WALBERLA_DOUBLE_ACCURACY: "OFF" WALBERLA_BUILD_WITH_PARMETIS: "OFF" WALBERLA_BUILD_WITH_METIS: "OFF" tags: + - cuda - docker @@ -1937,7 +2099,7 @@ doc: - mkdir $CI_PROJECT_DIR/build - cd $CI_PROJECT_DIR/build - cmake .. - - cmake . -LAH + - cmake . -LA - make doc stage: merge_request when: manual @@ -1967,7 +2129,7 @@ clang-tidy: - mkdir $CI_PROJECT_DIR/build - cd $CI_PROJECT_DIR/build - cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DWALBERLA_BUFFER_DEBUG=ON -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_TOOLS=ON -DWALBERLA_BUILD_WITH_MPI=ON -DWALBERLA_BUILD_WITH_OPENMP=ON -DCMAKE_BUILD_TYPE=Debug -DWALBERLA_BUILD_WITH_METIS=ON -DWALBERLA_BUILD_WITH_PARMETIS=ON -DWALBERLA_BUILD_WITH_OPENMESH=ON -DWALBERLA_DOUBLE_ACCURACY=ON - - cmake . -LAH + - cmake . -LA - utilities/filterCompileCommands.py compile_commands.json - run-clang-tidy.py -quiet | tee clang-tidy-output.txt stage: merge_request @@ -2009,7 +2171,7 @@ coverage: - cd build - if dpkg --compare-versions `ompi_info | head -2 | tail -1 | sed 's/[^0-9.]*\([0-9.]*\).*/\1/'` ge 1.10; then export MPIEXEC_PREFLAGS="--allow-run-as-root" ; fi - cmake .. -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_WITH_MPI=ON -DWALBERLA_BUILD_WITH_OPENMP=OFF -DCMAKE_BUILD_TYPE=Debug -DMPIEXEC_PREFLAGS=$MPIEXEC_PREFLAGS -DWALBERLA_BUILD_WITH_CODEGEN=OFF -DWALBERLA_BUILD_WITH_GCOV=ON - - cmake . -LAH + - cmake . -LA - make -j $NUM_BUILD_CORES -l $NUM_CORES - ctest -LE longrun --output-on-failure -j $NUM_CORES --timeout 3000 - cd .. @@ -2046,7 +2208,7 @@ coverage: - cmake --version - mkdir build - cd build - - cmake -LAH -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_WITH_MPI=$WALBERLA_BUILD_WITH_MPI -DWALBERLA_BUILD_WITH_OPENMP=$WALBERLA_BUILD_WITH_OPENMP -DWALBERLA_DOUBLE_ACCURACY=$WALBERLA_DOUBLE_ACCURACY -DWARNING_ERROR=ON -G "$CMAKE_GENERATOR" .. + - cmake -LA -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_WITH_MPI=$WALBERLA_BUILD_WITH_MPI -DWALBERLA_BUILD_WITH_OPENMP=$WALBERLA_BUILD_WITH_OPENMP -DWALBERLA_DOUBLE_ACCURACY=$WALBERLA_DOUBLE_ACCURACY -DWARNING_ERROR=ON -G "$CMAKE_GENERATOR" .. - MSBuild.exe walberla.sln /property:Configuration=$BUILD_CONFIGURATION /verbosity:minimal /maxcpucount:4 - ctest -LE $CTEST_EXCLUDE_LABELS -C $BUILD_CONFIGURATION --output-on-failure -j 4 variables: @@ -2278,7 +2440,7 @@ msvc-14.2_mpionly: - mkdir build - cd build - cmake .. -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_TOOLS=ON -DWALBERLA_BUILD_WITH_MPI=$WALBERLA_BUILD_WITH_MPI -DWALBERLA_BUILD_WITH_PYTHON=$WALBERLA_BUILD_WITH_PYTHON -DWALBERLA_BUILD_WITH_OPENMP=$WALBERLA_BUILD_WITH_OPENMP -DWALBERLA_BUILD_WITH_CUDA=$WALBERLA_BUILD_WITH_CUDA -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DWARNING_ERROR=ON - - cmake . -LAH + - cmake . -LA - make -j $NUM_BUILD_CORES -l $NUM_CORES - ctest -LE "$CTEST_EXCLUDE_LABELS|cuda" -C $CMAKE_BUILD_TYPE --output-on-failure -j $NUM_CORES tags: @@ -2410,7 +2572,7 @@ conda-py36-linux: - mkdir $CI_PROJECT_DIR/build - cd $CI_PROJECT_DIR/build - cmake .. -DWALBERLA_BUFFER_DEBUG=OFF -DWALBERLA_BUILD_TESTS=OFF -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=OFF -DWALBERLA_BUILD_TOOLS=OFF -DWALBERLA_BUILD_WITH_MPI=ON -DWALBERLA_BUILD_WITH_CUDA=OFF -DWALBERLA_BUILD_WITH_PYTHON=OFF -DWALBERLA_BUILD_WITH_OPENMP=OFF -DCMAKE_BUILD_TYPE=RELEASE -DMPIEXEC_PREFLAGS=$MPIEXEC_PREFLAGS -DWALBERLA_DOUBLE_ACCURACY=ON -DWARNING_ERROR=ON -DWALBERLA_BUILD_WITH_METIS=OFF -DWALBERLA_BUILD_WITH_PARMETIS=OFF -DWALBERLA_OPTIMIZE_FOR_LOCALHOST=ON -DWALBERLA_BUILD_WITH_FASTMATH=ON -DWALBERLA_BUILD_WITH_LTO=ON - - cmake . -LAH + - cmake . -LA - cd apps/benchmarks/GranularGas - make -j 20 - export PATH=$PATH:/usr/local/likwid/bin @@ -2471,7 +2633,7 @@ benchmark_ClangBuildAnalyzer: - mkdir $CI_PROJECT_DIR/build - cd $CI_PROJECT_DIR/build - cmake .. -DWALBERLA_BUFFER_DEBUG=OFF -DWALBERLA_BUILD_TESTS=ON -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=ON -DWALBERLA_BUILD_TOOLS=OFF -DWALBERLA_BUILD_WITH_MPI=ON -DWALBERLA_BUILD_WITH_CUDA=OFF -DWALBERLA_BUILD_WITH_PYTHON=OFF -DWALBERLA_BUILD_WITH_OPENMP=OFF -DCMAKE_BUILD_TYPE=RELEASE -DMPIEXEC_PREFLAGS=$MPIEXEC_PREFLAGS -DWALBERLA_DOUBLE_ACCURACY=ON -DWARNING_ERROR=ON -DWALBERLA_BUILD_WITH_METIS=OFF -DWALBERLA_BUILD_WITH_PARMETIS=OFF -DWALBERLA_OPTIMIZE_FOR_LOCALHOST=ON -DWALBERLA_BUILD_WITH_FASTMATH=ON -DWALBERLA_BUILD_WITH_LTO=ON -DCMAKE_CXX_FLAGS=-ftime-trace -G Ninja - - cmake . -LAH + - cmake . -LA - ClangBuildAnalyzer --start . - ninja all - ClangBuildAnalyzer --stop . CBA diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..2c0cd5c01 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/pybind11"] + path = extern/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c695d2c5..bbcea2971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +- Python Coupling now build upon pybind11. Boost.Python is no longer supported + - lbm module dropped from python coupling due to deprecation for a long time + - geometry, postprocessing and timeloop dropped from python coupling due to its low usage + - PEP8-ification of Python API. This means all keyword arguments are now in snake_case and not in CamelCase as before. + ## [4.1] - 2019-04-19 ### Added - Galerkin coarsening for Multigrid diff --git a/CMakeLists.txt b/CMakeLists.txt index 3427a1eb2..d866ab63d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,8 +75,6 @@ option ( WALBERLA_BUILD_WITH_GCOV "Enables gcov" option ( WALBERLA_BUILD_WITH_LTO "Enable link time optimizations" ) option ( WALBERLA_BUILD_WITH_OPENMP "Enable OpenMP support" ) option ( WALBERLA_BUILD_WITH_PYTHON "Support for embedding Python" ) -option ( WALBERLA_BUILD_WITH_PYTHON_MODULE "Build waLBerla python module" ) -option ( WALBERLA_BUILD_WITH_PYTHON_LBM "Include LBM module into python module" OFF ) option ( WALBERLA_BUILD_WITH_CODEGEN "Enable pystencils code generation" OFF ) @@ -100,6 +98,8 @@ option ( WALBERLA_OPTIMIZE_FOR_LOCALHOST "Enable compiler optimizations spcif option ( WALBERLA_LOG_SKIPPED "Log skipped cmake targets" ON ) +option ( WALBERLA_GIT_SUBMODULE_AUTO "Check submodules during cmake run" ON ) + # Installation Directory set ( CMAKE_INSTALL_PREFIX /usr/local/waLBerla CACHE STRING "The default installation directory." ) @@ -315,7 +315,6 @@ if( WALBERLA_CXX_COMPILER_IS_IBM ) add_flag ( CMAKE_CXX_FLAGS "-qsuppress=1586-267" ) # 1586-267 (I) Inlining of specified subprogram failed due to the presence of a C++ exception handler add_flag ( CMAKE_CXX_FLAGS "-qsuppress=1586-266" ) # 1586-266 (I) Inlining of specified subprogram failed due to the presence of a global label add_flag ( CMAKE_CXX_FLAGS "-qsuppress=1540-0724" ) # 1540-0724 (W) The non-type template argument "2147483648" of type "T" has wrapped [coming from boost/integer_traits.hpp] - add_flag ( CMAKE_CXX_FLAGS "-qsuppress=1540-0095" ) # 1540-0095 (W) The friend function declaration ... [coming from boost/mpl/map/aux_/map0.hpp] add_flag ( CMAKE_CXX_FLAGS "-qsuppress=1500-030" ) # 1500-030: (I) INFORMATION: [...] Additional optimization may be attained by recompiling and specifying MAXMEM option with a value greater than 8192. add_flag ( CMAKE_C_FLAGS "-qsuppress=1500-030" ) # 1500-030: (I) INFORMATION: [...] Additional optimization may be attained by recompiling and specifying MAXMEM option with a value greater than 8192. endif() @@ -587,53 +586,56 @@ endif() ############################################################################################################################# if ( WALBERLA_BUILD_WITH_PYTHON ) - set ( waLBerla_REQUIRED_MIN_PYTHON_VERSION "2.7") - - find_package( PythonInterp 3 QUIET) # search for Python3 first - find_package( PythonInterp QUIET) # fallback to any Python version - + find_package( PythonInterp 3.6 QUIET REQUIRED) find_package( PythonLibs QUIET REQUIRED) - if( PYTHONLIBS_VERSION_STRING VERSION_LESS ${waLBerla_REQUIRED_MIN_PYTHON_VERSION} ) - message( FATAL_ERROR "Found old python library: ${PYTHONLIBS_VERSION_STRING} need at least ${waLBerla_REQUIRED_MIN_PYTHON_VERSION}" ) - endif() - - option( WALBERLA_USE_PYTHON_DEBUG_LIBRARY "Make use of the python debug library" OFF ) - - if( WALBERLA_USE_PYTHON_DEBUG_LIBRARY ) - # you have to make sure this matches the settings you compiled boost with! - add_definitions( "-DBOOST_DEBUG_PYTHON" ) - endif() - if( NOT (PYTHON_LIBRARY AND PYTHON_INCLUDE_DIR ) ) message( FATAL_ERROR "Couldn't find any python library" ) endif() - SET( WALBERLA_BUILD_WITH_PYTHON 1 ) - include_directories( ${PYTHON_INCLUDE_DIR} ) - list ( APPEND SERVICE_LIBS ${PYTHON_LIBRARY} ) - - if( NOT WALBERLA_CXX_COMPILER_IS_MSVC ) - list ( APPEND SERVICE_LIBS -lutil ) - endif() - - if ( WALBERLA_BUILD_WITH_PYTHON_MODULE ) - # a python module is a shared library - so everything has to be compiled to position independent code - # otherwise linking the static libs into the shared lib will result in errors - if( NOT WALBERLA_CXX_COMPILER_IS_MSVC ) - add_flag ( CMAKE_CXX_FLAGS "-fPIC" ) - add_flag ( CMAKE_C_FLAGS "-fPIC" ) + execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import pybind11; print(pybind11._version.__version__)" + OUTPUT_VARIABLE pybind11_VERSION ERROR_QUIET RESULT_VARIABLE pybind11_VERSION_RESULT) + string(STRIP "${pybind11_VERSION}" pybind11_VERSION) + if(pybind11_VERSION_RESULT EQUAL "0" AND pybind11_VERSION VERSION_GREATER_EQUAL "2.6.0") + execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pybind11 --cmakedir + OUTPUT_VARIABLE PYBIND11_CMAKE_PATH) + string(STRIP "${PYBIND11_CMAKE_PATH}" PYBIND11_CMAKE_PATH) + find_package(pybind11 PATHS "${PYBIND11_CMAKE_PATH}" NO_DEFAULT_PATH REQUIRED) + else() + find_package(Git QUIET) + if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Update submodules as needed + if(WALBERLA_GIT_SUBMODULE_AUTO) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() + endif() endif() - endif() - if( MSVC10 ) - include(CMakeDependentOption) - CMAKE_DEPENDENT_OPTION( PYTHON_FIXED_HYPOT_REDEFINITION "fixed _hypot redefinition by python" OFF "WALBERLA_BUILD_WITH_PYTHON" OFF ) - if( NOT PYTHON_FIXED_HYPOT_REDEFINITION ) - message( WARNING "Make sure you modified your pyconfig.h that _hypot is not redefined -> see: http://connect.microsoft.com/VisualStudio/feedback/details/633988/warning-in-math-h-line-162-re-nonstandard-extensions-used" ) + if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/pybind11/CMakeLists.txt") + if(EXISTS "${PROJECT_SOURCE_DIR}/.git") + message(FATAL_ERROR "Please update git submodules or install pybind11 via pip.") + else() + message(FATAL_ERROR "Please install pybind11 via pip or download it to ${PROJECT_SOURCE_DIR}/extern/pybind11.") + endif() endif() + + add_subdirectory(extern/pybind11) + + # if pybind11 was installed into ${PYTHON_INCLUDE_DIR} (e.g. by pip), that will have a higher priority than the Git submodule unless we reorder the search path + # introduced in 2.6.0 (https://github.com/pybind/pybind11/issues/2709), fixed in 2.6.2 (https://github.com/pybind/pybind11/pull/2716) + set_property( TARGET pybind11::pybind11 + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/extern/pybind11/include" "${PYTHON_INCLUDE_DIR}") endif() + # a python module is a shared library - so everything has to be compiled to position independent code + # otherwise linking the static libs into the shared lib will result in errors + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if(WALBERLA_BUILD_DOC) # Sphinx documentation @@ -659,16 +661,6 @@ else() list ( APPEND waLBerla_OPTIONAL_BOOST_COMPONENTS system ) endif() -if ( WALBERLA_BUILD_WITH_PYTHON ) - if( WALBERLA_CXX_COMPILER_IS_MSVC ) - get_filename_component(PYTHON_REQUIRED_LIB ${PYTHON_LIBRARY} NAME_WE) - list( APPEND waLBerla_REQUIRED_BOOST_COMPONENTS ${PYTHON_REQUIRED_LIB} ) - elseif( WALBERLA_USE_STD_FILESYSTEM OR WALBERLA_USE_STD_EXPERIMENTAL_FILESYSTEM ) - list( APPEND waLBerla_REQUIRED_BOOST_COMPONENTS system ) - list( REMOVE_ITEM waLBerla_OPTIONAL_BOOST_COMPONENTS system ) - endif() -endif() - # This variable is necessary, if the CMAKE version used is not aware of a more recent boost version (keep this up to date!) set ( Boost_ADDITIONAL_VERSIONS "1.45" "1.45.0" "1.46" "1.46.0" "1.46.1" "1.47" "1.47.0" "1.48" "1.48.0" "1.49" "1.49.0" @@ -727,18 +719,6 @@ if ( Boost_FOUND ) endif() add_definitions ( -DBOOST_ALL_NO_LIB ) # Disable Boost auto-linking (CMAKE does that for us...) - #fix for static lib usage: http://stackoverflow.com/questions/11812463/boost-python-link-errors-under-windows-msvc10 - if( PYTHONLIBS_FOUND AND Boost_USE_STATIC_LIBS) - add_definitions( -DBOOST_PYTHON_STATIC_LIB ) - endif() - - #fix for strange link behaviour of boost to python: boost only links to 'pyhton*.lib' and not to the absolute path - if( WIN32 AND PYTHONLIBS_FOUND ) - get_filename_component( PYTHON_LIBRARY_DIR ${PYTHON_INCLUDE_DIR} PATH ) - link_directories( ${PYTHON_LIBRARY_DIR}/libs ) - list( APPEND LINK_DIRS ${PYTHON_LIBRARY_DIR}/libs ) - endif() - set( WALBERLA_BUILD_WITH_BOOST TRUE CACHE INTERNAL "Build with Boost" ) else( Boost_FOUND ) if( (WALBERLA_USE_STD_EXPERIMENTAL_FILESYSTEM OR WALBERLA_USE_STD_FILESYSTEM) AND (WALBERLA_USE_STD_EXPERIMENTAL_ANY OR WALBERLA_USE_STD_ANY) AND (WALBERLA_USE_STD_EXPERIMENTAL_OPTIONAL OR WALBERLA_USE_STD_OPTIONAL) AND NOT WALBERLA_BUILD_WITH_PYTHON) @@ -754,31 +734,6 @@ else( Boost_FOUND ) endif( Boost_FOUND ) -# Check if Python3 found and look for according boost python library -if ( WALBERLA_BUILD_WITH_PYTHON AND NOT WALBERLA_CXX_COMPILER_IS_MSVC) - SET(_boost_MULTITHREADED "") - if (Boost_USE_MULTITHREADED OR Boost_USE_MULTITHREADED_LIBRARY) - SET(_boost_MULTITHREADED "-mt") - endif() - if( PYTHON_LIBRARY MATCHES "python3" ) - string(REPLACE "." ";" VERSION_LIST ${PYTHONLIBS_VERSION_STRING}) - list(GET VERSION_LIST 0 PY_VER_MAJOR) - list(GET VERSION_LIST 1 PY_VER_MINOR) - find_library( BOOST_PYTHON_LIBRARY NAMES - boost_python${PY_VER_MAJOR}${PY_VER_MINOR}${_boost_MULTITHREADED} - boost_python-py${PY_VER_MAJOR}${PY_VER_MINOR}${_boost_MULTITHREADED} - boost_python${PY_VER_MAJOR}${_boost_MULTITHREADED} - boost_python${_boost_MULTITHREADED} - PATHS ${Boost_LIBRARY_DIRS} NO_DEFAULT_PATH ) - else() - find_library( BOOST_PYTHON_LIBRARY NAMES boost_python${_boost_MULTITHREADED} - PATHS ${Boost_LIBRARY_DIRS} NO_DEFAULT_PATH ) - endif() - message(STATUS "Using Boost Python Library ${BOOST_PYTHON_LIBRARY}" ) - list ( APPEND SERVICE_LIBS ${BOOST_PYTHON_LIBRARY} ) -endif() - - ############################################################################################################################ diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 58953c4bb..178380b57 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -32,6 +32,4 @@ endif() # Python module if ( WALBERLA_BUILD_WITH_PYTHON ) add_subdirectory( pythonmodule ) - # no else with "EXLUDE_FROM_ALL" here, since if WALBERLA_BUILD_WITH_PYTHON_MODULE is not activated - # waLBerla was build without -fPIC , so no linking into shared library is possible endif() \ No newline at end of file diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt index aabba8b45..ec223734f 100644 --- a/apps/benchmarks/CMakeLists.txt +++ b/apps/benchmarks/CMakeLists.txt @@ -15,7 +15,7 @@ add_subdirectory( PoiseuilleChannel ) add_subdirectory( ProbeVsExtraMessage ) add_subdirectory( SchaeferTurek ) add_subdirectory( UniformGrid ) -if ( WALBERLA_BUILD_WITH_CODEGEN ) +if ( WALBERLA_BUILD_WITH_CODEGEN AND WALBERLA_BUILD_WITH_PYTHON ) add_subdirectory( UniformGridGenerated ) add_subdirectory( PhaseFieldAllenCahn ) endif() diff --git a/apps/benchmarks/FieldCommunication/CMakeLists.txt b/apps/benchmarks/FieldCommunication/CMakeLists.txt index 35a2f698a..edb815612 100644 --- a/apps/benchmarks/FieldCommunication/CMakeLists.txt +++ b/apps/benchmarks/FieldCommunication/CMakeLists.txt @@ -4,4 +4,4 @@ waLBerla_link_files_to_builddir( "*.py" ) waLBerla_add_executable ( NAME FieldCommunication - DEPENDS blockforest core domain_decomposition field postprocessing sqlite ) + DEPENDS blockforest core domain_decomposition field postprocessing sqlite python_coupling ) diff --git a/apps/benchmarks/FieldCommunication/FieldCommunication.cpp b/apps/benchmarks/FieldCommunication/FieldCommunication.cpp index fabde23e0..c9b3cc87a 100644 --- a/apps/benchmarks/FieldCommunication/FieldCommunication.cpp +++ b/apps/benchmarks/FieldCommunication/FieldCommunication.cpp @@ -36,7 +36,7 @@ class SingleMessageBufferedScheme public: typedef Stencil_T Stencil; - SingleMessageBufferedScheme( const weak_ptr_wrapper< StructuredBlockForest > & bf, const int tag = 17953 ) + SingleMessageBufferedScheme( const weak_ptr< StructuredBlockForest > & bf, const int tag = 17953 ) : blockForest_( bf ), tag_( tag ) {} inline void addDataToCommunicate( const shared_ptr< communication::UniformPackInfo > &packInfo ) @@ -67,7 +67,7 @@ public: private: std::vector< shared_ptr< UniformBufferedScheme< Stencil>> > schemes_; - weak_ptr_wrapper< StructuredBlockForest > blockForest_; + weak_ptr< StructuredBlockForest > blockForest_; int tag_; }; diff --git a/apps/benchmarks/PhaseFieldAllenCahn/CMakeLists.txt b/apps/benchmarks/PhaseFieldAllenCahn/CMakeLists.txt index 629c9ec0a..951f04a22 100644 --- a/apps/benchmarks/PhaseFieldAllenCahn/CMakeLists.txt +++ b/apps/benchmarks/PhaseFieldAllenCahn/CMakeLists.txt @@ -16,6 +16,7 @@ if (WALBERLA_BUILD_WITH_CUDA) waLBerla_add_executable(NAME benchmark_multiphase FILES benchmark_multiphase.cpp InitializerFunctions.cpp multiphase_codegen.py DEPENDS blockforest core cuda field postprocessing lbm geometry timeloop gui BenchmarkPhaseFieldCodeGenGPU) + set_target_properties(benchmark_multiphase PROPERTIES CXX_VISIBILITY_PRESET hidden) else () waLBerla_generate_target_from_python(NAME BenchmarkPhaseFieldCodeGenCPU FILE multiphase_codegen.py @@ -31,5 +32,6 @@ else () waLBerla_add_executable(NAME benchmark_multiphase FILES benchmark_multiphase.cpp InitializerFunctions.cpp multiphase_codegen.py DEPENDS blockforest core field postprocessing lbm geometry timeloop gui BenchmarkPhaseFieldCodeGenCPU) + set_target_properties(benchmark_multiphase PROPERTIES CXX_VISIBILITY_PRESET hidden) endif (WALBERLA_BUILD_WITH_CUDA) diff --git a/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.cpp b/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.cpp index 45e91e25a..5f7e5b2f6 100644 --- a/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.cpp +++ b/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.cpp @@ -25,8 +25,6 @@ #include "field/communication/PackInfo.h" #include "field/vtk/VTKWriter.h" -#include "python_coupling/DictWrapper.h" - namespace walberla { using PhaseField_T = GhostLayerField< real_t, 1 >; diff --git a/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.h b/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.h index 4ba7896e7..f30d41e17 100644 --- a/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.h +++ b/apps/benchmarks/PhaseFieldAllenCahn/InitializerFunctions.h @@ -18,15 +18,12 @@ // //====================================================================================================================== -#include "core/Environment.h" -#include "core/logging/Initialization.h" #include "core/math/Constants.h" #include "field/FlagField.h" #include "field/communication/PackInfo.h" #include "field/vtk/VTKWriter.h" -#include "python_coupling/DictWrapper.h" #pragma once namespace walberla diff --git a/apps/benchmarks/PhaseFieldAllenCahn/benchmark_multiphase.cpp b/apps/benchmarks/PhaseFieldAllenCahn/benchmark_multiphase.cpp index 944ca0a0b..3513c15f2 100644 --- a/apps/benchmarks/PhaseFieldAllenCahn/benchmark_multiphase.cpp +++ b/apps/benchmarks/PhaseFieldAllenCahn/benchmark_multiphase.cpp @@ -27,8 +27,6 @@ #include "field/AddToStorage.h" #include "field/FlagField.h" -#include "field/communication/PackInfo.h" -#include "field/python/Exports.h" #include "field/vtk/VTKWriter.h" #include "geometry/InitBoundaryHandling.h" diff --git a/apps/benchmarks/PhaseFieldAllenCahn/profiling.py b/apps/benchmarks/PhaseFieldAllenCahn/profiling.py index 71cdba3f3..f8e6bed30 100644 --- a/apps/benchmarks/PhaseFieldAllenCahn/profiling.py +++ b/apps/benchmarks/PhaseFieldAllenCahn/profiling.py @@ -27,8 +27,6 @@ class Scenario: self.scenario = 1 # 1 rising bubble, 2 RTI self.config_dict = self.config() - self.csv_file = "benchmark.csv" - @wlb.member_callback def config(self): return { diff --git a/apps/benchmarks/UniformGridGPU/CMakeLists.txt b/apps/benchmarks/UniformGridGPU/CMakeLists.txt index 4a6390633..9fe54701f 100644 --- a/apps/benchmarks/UniformGridGPU/CMakeLists.txt +++ b/apps/benchmarks/UniformGridGPU/CMakeLists.txt @@ -1,6 +1,6 @@ waLBerla_link_files_to_builddir( "*.prm" ) -#waLBerla_link_files_to_builddir( "simulation_setup" ) +waLBerla_link_files_to_builddir( "simulation_setup" ) foreach (config srt trt mrt smagorinsky entropic smagorinsky_noopt entropic_kbc_n4 @@ -25,6 +25,7 @@ foreach (config srt trt mrt smagorinsky entropic smagorinsky_noopt entropic_kbc_ waLBerla_add_executable(NAME UniformGridBenchmarkGPU_${config} FILES UniformGridGPU.cpp DEPENDS blockforest boundary core cuda domain_decomposition field geometry timeloop vtk gui UniformGridGPUGenerated_${config}) + set_target_properties( UniformGridBenchmarkGPU_${config} PROPERTIES CXX_VISIBILITY_PRESET hidden) endforeach () @@ -46,4 +47,5 @@ foreach (config srt trt mrt smagorinsky entropic) waLBerla_add_executable(NAME UniformGridBenchmarkGPU_AA_${config} FILES UniformGridGPU_AA.cpp DEPENDS blockforest boundary core cuda domain_decomposition field geometry timeloop vtk gui UniformGridGPUGenerated_AA_${config}) + set_target_properties( UniformGridBenchmarkGPU_AA_${config} PROPERTIES CXX_VISIBILITY_PRESET hidden) endforeach () diff --git a/apps/benchmarks/UniformGridGPU/UniformGridGPU_Communication.h b/apps/benchmarks/UniformGridGPU/UniformGridGPU_Communication.h index db0ec86e6..aadf51331 100644 --- a/apps/benchmarks/UniformGridGPU/UniformGridGPU_Communication.h +++ b/apps/benchmarks/UniformGridGPU/UniformGridGPU_Communication.h @@ -30,7 +30,7 @@ template<typename StencilType, typename GPUFieldType > class UniformGridGPU_Communication { public: - explicit UniformGridGPU_Communication(weak_ptr_wrapper<StructuredBlockForest> bf, const BlockDataID & bdId, + explicit UniformGridGPU_Communication(weak_ptr<StructuredBlockForest> bf, const BlockDataID & bdId, CommunicationSchemeType commSchemeType, bool cudaEnabledMPI = false) : _commSchemeType(commSchemeType), _cpuCommunicationScheme(nullptr), _gpuPackInfo(nullptr), _gpuCommunicationScheme(nullptr), _directScheme(nullptr) diff --git a/apps/benchmarks/UniformGridGenerated/CMakeLists.txt b/apps/benchmarks/UniformGridGenerated/CMakeLists.txt index ee60a84f5..f964f242a 100644 --- a/apps/benchmarks/UniformGridGenerated/CMakeLists.txt +++ b/apps/benchmarks/UniformGridGenerated/CMakeLists.txt @@ -15,6 +15,7 @@ foreach(config trt smagorinsky mrt entropic_kbc_n4 cumulant ) waLBerla_add_executable ( NAME UniformGridBenchmarkGenerated_${config} FILES UniformGridGenerated.cpp DEPENDS blockforest boundary core domain_decomposition field geometry timeloop vtk gui - UniformGridGenerated_${config}) + UniformGridGenerated_${config} python_coupling) + set_target_properties(UniformGridBenchmarkGenerated_${config} PROPERTIES CXX_VISIBILITY_PRESET hidden) endforeach() diff --git a/apps/pythonmodule/CMakeLists.txt b/apps/pythonmodule/CMakeLists.txt index 820784e2b..38290ffa5 100644 --- a/apps/pythonmodule/CMakeLists.txt +++ b/apps/pythonmodule/CMakeLists.txt @@ -1,43 +1,34 @@ # waLBerla Python module -if ( WALBERLA_BUILD_WITH_PYTHON_MODULE ) - - set(PYTHON_MODULE_DEPENDENCIES blockforest boundary domain_decomposition core field geometry lbm postprocessing python_coupling timeloop vtk) - if (WALBERLA_BUILD_WITH_CUDA) - set(PYTHON_MODULE_DEPENDENCIES ${PYTHON_MODULE_DEPENDENCIES} cuda) - endif() - - if (WALBERLA_BUILD_WITH_OPENMESH) - set(PYTHON_MODULE_DEPENDENCIES ${PYTHON_MODULE_DEPENDENCIES} mesh pe) +if ( WALBERLA_BUILD_WITH_PYTHON ) + if ( WALBERLA_BUILD_WITH_CUDA ) + set(PYTHON_MODULE_DEPENDENCIES blockforest boundary domain_decomposition core field python_coupling timeloop vtk cuda) + else() + set(PYTHON_MODULE_DEPENDENCIES blockforest boundary domain_decomposition core field python_coupling timeloop vtk) endif() if( WALBERLA_CXX_COMPILER_IS_MSVC ) - set ( pythonModules ${PYTHON_MODULE_DEPENDENCIES}) + set ( pythonModules ${PYTHON_MODULE_DEPENDENCIES}) elseif( APPLE ) - set ( pythonModules "-Wl,-force_load" ${PYTHON_MODULE_DEPENDENCIES}) + set ( pythonModules "-Wl,-force_load" ${PYTHON_MODULE_DEPENDENCIES}) else() - set ( pythonModules "-Wl,-whole-archive" ${PYTHON_MODULE_DEPENDENCIES} "-Wl,-no-whole-archive" ) + set ( pythonModules "-Wl,-whole-archive" ${PYTHON_MODULE_DEPENDENCIES} "-Wl,-no-whole-archive" ) endif() - if( WALBERLA_BUILD_WITH_PYTHON_LBM ) - add_library( walberla_cpp SHARED PythonModuleWithLbmModule.cpp ) - else() - add_library( walberla_cpp SHARED PythonModule.cpp ) - endif() + add_library( walberla_cpp SHARED PythonModule.cpp ) - target_link_libraries( walberla_cpp ${WALBERLA_LINK_LIBRARIES_KEYWORD} ${pythonModules} ${SERVICE_LIBS} ) - set_target_properties( walberla_cpp PROPERTIES PREFIX "") - if ( APPLE ) - set_target_properties( walberla_cpp PROPERTIES SUFFIX ".so") - endif() + target_link_libraries( walberla_cpp ${WALBERLA_LINK_LIBRARIES_KEYWORD} ${pythonModules} ${SERVICE_LIBS} ) + + set_target_properties( + walberla_cpp PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + ) set_target_properties( walberla_cpp PROPERTIES MACOSX_RPATH TRUE ) + set_target_properties( walberla_cpp PROPERTIES CXX_VISIBILITY_PRESET hidden) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/setup.py ${CMAKE_CURRENT_BINARY_DIR}/setup.py ) add_custom_target( pythonModule ALL ${PYTHON_EXECUTABLE} setup.py build DEPENDS walberla_cpp ) - add_custom_target( pythonModuleInstall ${PYTHON_EXECUTABLE} setup.py install DEPENDS walberla_cpp ) - - add_test( NAME PythonModuleTest - COMMAND ${PYTHON_EXECUTABLE} -m unittest discover -v -s ${walberla_SOURCE_DIR}/python/waLBerla_tests ) + add_custom_target( pythonModuleInstall ${PYTHON_EXECUTABLE} setup.py install --user DEPENDS walberla_cpp ) endif() diff --git a/apps/pythonmodule/PythonModule.cpp b/apps/pythonmodule/PythonModule.cpp index 43afcff72..419b1b377 100644 --- a/apps/pythonmodule/PythonModule.cpp +++ b/apps/pythonmodule/PythonModule.cpp @@ -15,125 +15,84 @@ // //! \file PythonModule.cpp //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== -#include "python_coupling/PythonWrapper.h" -#include "waLBerlaDefinitions.h" -#include "blockforest/python/Exports.h" #include "field/GhostLayerField.h" -#include "field/python/Exports.h" -#include "mesh/python/Exports.h" -#include "geometry/python/Exports.h" -#include "postprocessing/python/Exports.h" + #include "python_coupling/Manager.h" +#include "python_coupling/export/BlockForestExport.h" +#include "python_coupling/export/FieldExports.h" +#include "python_coupling/export/VTKExport.h" #include "python_coupling/helper/ModuleInit.h" + #include "stencil/all.h" -#include "timeloop/python/Exports.h" -#include "vtk/python/Exports.h" #ifdef WALBERLA_BUILD_WITH_CUDA -#include "cuda/python/Exports.h" + #include "python_coupling/export/CUDAExport.h" #endif -#include <boost/mpl/vector.hpp> -#include <boost/mpl/insert_range.hpp> - - -namespace bmpl = boost::mpl; using namespace walberla; -typedef bmpl::vector< - Field<walberla::real_t,1>, - Field<walberla::real_t,2>, - Field<walberla::real_t,3>, - Field<walberla::real_t,4>, - Field<walberla::real_t,5>, - Field<walberla::real_t,6>, - Field<walberla::real_t,9>, - Field<walberla::real_t,15>, - Field<walberla::real_t,19>, - Field<walberla::real_t,27>, - - Field<walberla::int8_t,1>, - Field<walberla::int16_t,1>, - Field<walberla::int32_t,1>, - - Field<walberla::int64_t,1>, - Field<walberla::int64_t,2>, - Field<walberla::int64_t,3>, - Field<walberla::int64_t,4>, - - Field<walberla::uint8_t,1>, - Field<walberla::uint16_t,1>, - Field<walberla::uint32_t,1> - > FieldTypes; - -typedef bmpl::vector< - GhostLayerField<walberla::real_t,1>, - GhostLayerField<walberla::real_t,3> - > FieldTypesForMeshGeneration; - - -typedef bmpl::vector< FlagField<walberla::uint8_t>, - FlagField<walberla::uint16_t> > FlagFieldTypes; - -typedef bmpl::vector< stencil::D2Q5, - stencil::D2Q9, - stencil::D3Q7, - stencil::D3Q19, - stencil::D3Q27 > Stencils; - -typedef GhostLayerField<walberla::real_t,3> VecField_T; - - -using namespace walberla; +#define FIELD_TYPES \ + Field<walberla::real_t,1>,\ + Field<walberla::real_t,2>,\ + Field<walberla::real_t,3>,\ + Field<walberla::real_t,4>,\ + Field<walberla::real_t,5>,\ + Field<walberla::real_t,6>,\ + Field<walberla::real_t,9>,\ + Field<walberla::real_t,15>,\ + Field<walberla::real_t,19>,\ + Field<walberla::real_t,27>,\ + Field<walberla::int8_t,1>,\ + Field<walberla::int16_t,1>,\ + Field<walberla::int32_t,1>,\ + Field<walberla::int64_t,1>,\ + Field<walberla::int64_t,2>,\ + Field<walberla::int64_t,3>,\ + Field<walberla::int64_t,4>,\ + Field<walberla::uint8_t,1>,\ + Field<walberla::uint16_t,1>,\ + Field<walberla::uint32_t,1> + +#define GPU_FIELD_TYPES \ + GPUField<double>,\ + GPUField<float>,\ + GPUField<int8_t>,\ + GPUField<int16_t>,\ + GPUField<int32_t>,\ + GPUField<int64_t>,\ + GPUField<uint8_t>,\ + GPUField<uint16_t>,\ + GPUField<uint32_t>,\ + GPUField<uint64_t> struct InitObject { InitObject() { - namespace bmpl = boost::mpl; - auto pythonManager = python_coupling::Manager::instance(); - // Field - pythonManager->addExporterFunction( field::exportModuleToPython<FieldTypes> ); - pythonManager->addExporterFunction( field::exportGatherFunctions<FieldTypes> ); - pythonManager->addBlockDataConversion<FieldTypes>() ; - + pythonManager->addExporterFunction( field::exportModuleToPython<FIELD_TYPES> ); + pythonManager->addExporterFunction( field::exportGatherFunctions<FIELD_TYPES> ); + pythonManager->addBlockDataConversion<FIELD_TYPES>(); // Blockforest - pythonManager->addExporterFunction( blockforest::exportModuleToPython<Stencils> ); - - // Geometry - pythonManager->addExporterFunction( geometry::exportModuleToPython ); - + pythonManager->addExporterFunction(blockforest::exportModuleToPython<stencil::D2Q5, stencil::D2Q9, stencil::D3Q7, stencil::D3Q19, stencil::D3Q27>); // VTK pythonManager->addExporterFunction( vtk::exportModuleToPython ); - - // Postprocessing - pythonManager->addExporterFunction( postprocessing::exportModuleToPython<FieldTypesForMeshGeneration, FlagFieldTypes> ); - - // Timeloop - pythonManager->addExporterFunction( timeloop::exportModuleToPython ); - -#ifdef WALBERLA_BUILD_WITH_OPENMESH - pythonManager->addExporterFunction( mesh::exportModuleToPython<FlagFieldTypes> ); -#endif - -#ifdef WALBERLA_BUILD_WITH_CUDA - using walberla::cuda::GPUField; - typedef bmpl::vector<GPUField<double>, GPUField<float>, - GPUField<int8_t>, GPUField<int16_t>, GPUField<int32_t>, GPUField<int64_t>, - GPUField<uint8_t>, GPUField<uint16_t>, GPUField<uint32_t>,GPUField<uint64_t> > GPUFields; - - pythonManager->addExporterFunction( cuda::exportModuleToPython<GPUFields, FieldTypes> ); - pythonManager->addBlockDataConversion<GPUFields>(); -#endif - + #ifdef WALBERLA_BUILD_WITH_CUDA + using walberla::cuda::GPUField; + + pythonManager->addExporterFunction( cuda::exportModuleToPython<GPU_FIELD_TYPES> ); + pythonManager->addExporterFunction( cuda::exportCopyFunctionsToPython<FIELD_TYPES> ); + pythonManager->addBlockDataConversion<GPU_FIELD_TYPES>(); + #endif + // python_coupling::initWalberlaForPythonModule(); + } }; -InitObject globalInitObject; - +InitObject globalInitObject; \ No newline at end of file diff --git a/apps/pythonmodule/PythonModuleWithLbmModule.cpp b/apps/pythonmodule/PythonModuleWithLbmModule.cpp deleted file mode 100644 index 03301a723..000000000 --- a/apps/pythonmodule/PythonModuleWithLbmModule.cpp +++ /dev/null @@ -1,234 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonModule.cpp -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== -#include "python_coupling/PythonWrapper.h" -#include "waLBerlaDefinitions.h" -#include "blockforest/python/Exports.h" -#include "field/GhostLayerField.h" -#include "field/python/Exports.h" -#include "geometry/python/Exports.h" -#include "lbm/all.h" -#include "lbm/lattice_model/ForceModel.h" -#include "lbm/python/Exports.h" -#include "postprocessing/python/Exports.h" -#include "python_coupling/Manager.h" -#include "python_coupling/helper/ModuleInit.h" -#include "stencil/all.h" -#include "timeloop/python/Exports.h" -#include "vtk/python/Exports.h" - -#include <boost/mpl/vector.hpp> -#include <boost/mpl/insert_range.hpp> - - -namespace bmpl = boost::mpl; -using namespace walberla; - -typedef bmpl::vector< - Field<walberla::real_t,1>, - Field<walberla::real_t,2>, - Field<walberla::real_t,3>, - Field<walberla::real_t,4>, - Field<walberla::real_t,5>, - Field<walberla::real_t,9>, - Field<walberla::real_t,19>, - Field<walberla::real_t,27>, - - Field<walberla::int8_t,1>, - Field<walberla::int16_t,1>, - Field<walberla::int32_t,1>, - - Field<walberla::int64_t,1>, - Field<walberla::int64_t,2>, - Field<walberla::int64_t,3>, - Field<walberla::int64_t,4>, - Field<walberla::int64_t,5>, - - Field<walberla::uint8_t,1>, - Field<walberla::uint16_t,1>, - Field<walberla::uint32_t,1> - > FieldTypes; - - -typedef bmpl::vector< - GhostLayerField<walberla::real_t,1>, - GhostLayerField<walberla::real_t,3> - > FieldTypesForMeshGeneration; - - -typedef bmpl::vector< FlagField<walberla::uint8_t>, - FlagField<walberla::uint16_t> > FlagFieldTypes; - -typedef bmpl::vector< stencil::D2Q5, - stencil::D2Q9, - stencil::D3Q7, - stencil::D3Q19, - stencil::D3Q27 > Stencils; - -typedef lbm::collision_model::SRTField< GhostLayerField<walberla::real_t,1> > SRTField_T; - -typedef GhostLayerField<walberla::real_t,3> VecField_T; - - -// ------------------------- D2Q9 ----------------------------------------------------------------------------- - -typedef bmpl::list< lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::None, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::LuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, true, lbm::force_model::None, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, true, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, true, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::GuoField<VecField_T>, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, true, lbm::force_model::LuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::SRT, true, lbm::force_model::None, 1>, - lbm::D2Q9 < lbm::collision_model::SRT, false, lbm::force_model::None, 1> -> LM_D2Q19_SRT; - -typedef bmpl::list< lbm::D2Q9 < lbm::collision_model::TRT, false, lbm::force_model::None, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, false, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, false, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, false, lbm::force_model::LuoConstant, 2>, - - lbm::D2Q9 < lbm::collision_model::TRT, true, lbm::force_model::None, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, true, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, true, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, true, lbm::force_model::LuoConstant, 2>, - lbm::D2Q9 < lbm::collision_model::TRT, true, lbm::force_model::None, 1>, - lbm::D2Q9 < lbm::collision_model::TRT, false, lbm::force_model::None, 1> -> LM_D2Q19_TRT; - -typedef lbm::collision_model::SRTField<GhostLayerField<real_t,1> > SRTFieldCollisionModel; -typedef bmpl::list< lbm::D2Q9 < SRTFieldCollisionModel, false, lbm::force_model::None, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, false, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, false, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, false, lbm::force_model::LuoConstant, 2>, - - lbm::D2Q9 < SRTFieldCollisionModel, true, lbm::force_model::None, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, true, lbm::force_model::SimpleConstant, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, true, lbm::force_model::GuoConstant, 2>, - lbm::D2Q9 < SRTFieldCollisionModel, true, lbm::force_model::LuoConstant, 2> -> LM_D2Q9_SRTField; - -// ------------------------- D3Q19 ---------------------------------------------------------------------------- - - -typedef bmpl::list< lbm::D3Q19 < lbm::collision_model::SRT, false, lbm::force_model::None, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, false, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, false, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, false, lbm::force_model::LuoConstant, 2>, - - lbm::D3Q19 < lbm::collision_model::SRT, true, lbm::force_model::None, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, true, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, true, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < lbm::collision_model::SRT, true, lbm::force_model::LuoConstant, 2> -> LM_D3Q19_SRT; - -typedef bmpl::list< lbm::D3Q19 < lbm::collision_model::TRT, false, lbm::force_model::None, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, false, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, false, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, false, lbm::force_model::LuoConstant, 2>, - - lbm::D3Q19 < lbm::collision_model::TRT, true, lbm::force_model::None, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, true, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, true, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < lbm::collision_model::TRT, true, lbm::force_model::LuoConstant, 2> -> LM_D3Q19_TRT; - - -typedef bmpl::list<lbm::D3Q19 < lbm::collision_model::D3Q19MRT, false, lbm::force_model::None, 2>, - lbm::D3Q19 < lbm::collision_model::D3Q19MRT, false, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < lbm::collision_model::D3Q19MRT, false, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < lbm::collision_model::D3Q19MRT, false, lbm::force_model::LuoConstant, 2>, - - lbm::D3Q27 < lbm::collision_model::D3Q27Cumulant, true, lbm::force_model::None, 2> - -> LM_D3Q19_Extra; - - - -typedef lbm::collision_model::SRTField<GhostLayerField<real_t,1> > SRTFieldCollisionModel; -typedef bmpl::list< lbm::D3Q19 < SRTFieldCollisionModel, false, lbm::force_model::None, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, false, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, false, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, false, lbm::force_model::LuoConstant, 2>, - - lbm::D3Q19 < SRTFieldCollisionModel, true, lbm::force_model::None, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, true, lbm::force_model::SimpleConstant, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, true, lbm::force_model::GuoConstant, 2>, - lbm::D3Q19 < SRTFieldCollisionModel, true, lbm::force_model::LuoConstant, 2> -> LM_D3Q19_SRTField; - - -typedef bmpl::insert_range< LM_D2Q19_SRT, bmpl::end< LM_D2Q19_SRT >::type, LM_D2Q19_TRT> ::type D2Q9_SRT_TRT; -typedef bmpl::insert_range< D2Q9_SRT_TRT, bmpl::end< D2Q9_SRT_TRT >::type, LM_D2Q9_SRTField>::type LatticeModels2D; - - -typedef bmpl::insert_range< LM_D3Q19_SRT, bmpl::end< LM_D3Q19_SRT > ::type, LM_D3Q19_TRT>::type D3Q19_SRT_TRT; -typedef bmpl::insert_range< D3Q19_SRT_TRT, bmpl::end< D3Q19_SRT_TRT >::type, LM_D3Q19_Extra>::type D3Q19_SRT_TRT_MRT; -typedef bmpl::insert_range< D3Q19_SRT_TRT_MRT, bmpl::end< D3Q19_SRT_TRT_MRT >::type, LM_D3Q19_SRTField>::type LatticeModels3D; - - -typedef bmpl::insert_range< LatticeModels2D, bmpl::end< LatticeModels2D >::type, LatticeModels3D>::type LatticeModels; - -typedef typename lbm::AdaptorsFromLatticeModels<LatticeModels>::type LBMAdaptors; - - - -using namespace walberla; - -struct InitObject -{ - InitObject() - { - namespace bmpl = boost::mpl; - - auto pythonManager = python_coupling::Manager::instance(); - - // Field - pythonManager->addExporterFunction( field::exportModuleToPython<FieldTypes> ); - pythonManager->addExporterFunction( field::exportGatherFunctions<bmpl::joint_view<FieldTypes,LBMAdaptors>::type > ); - pythonManager->addBlockDataConversion< bmpl::joint_view<FieldTypes,LBMAdaptors>::type >() ; - - // Blockforest - pythonManager->addExporterFunction( blockforest::exportModuleToPython<Stencils> ); - - // Geometry - pythonManager->addExporterFunction( geometry::exportModuleToPython ); - - // VTK - pythonManager->addExporterFunction( vtk::exportModuleToPython ); - - // LBM - typedef bmpl::vector< FlagField<walberla::uint8_t> > FlagFieldTypesForLBM; - pythonManager->addExporterFunction( lbm::exportBasic<LatticeModels, FlagFieldTypesForLBM> ); - pythonManager->addExporterFunction( lbm::exportBoundary<LatticeModels, FlagFieldTypesForLBM> ); - pythonManager->addExporterFunction( lbm::exportSweeps<LatticeModels, FlagFieldTypesForLBM> ); - - // Postprocessing - pythonManager->addExporterFunction( postprocessing::exportModuleToPython<FieldTypesForMeshGeneration, FlagFieldTypes> ); - - // Timeloop - pythonManager->addExporterFunction( timeloop::exportModuleToPython ); - - python_coupling::initWalberlaForPythonModule(); - } -}; -InitObject globalInitObject; - diff --git a/apps/pythonmodule/setup.py b/apps/pythonmodule/setup.py index 5929b0fb9..066669c75 100644 --- a/apps/pythonmodule/setup.py +++ b/apps/pythonmodule/setup.py @@ -1,28 +1,30 @@ -from distutils.core import setup -import shutil -from os.path import exists, join -import platform import sys +import platform +from os.path import exists, join +import shutil + +from setuptools import setup # The following variables are configure by CMake walberla_source_dir = "${walberla_SOURCE_DIR}" walberla_binary_dir = "${CMAKE_CURRENT_BINARY_DIR}" +suffix = "${PYTHON_MODULE_EXTENSION}" +prefix = "${PYTHON_MODULE_PREFIX}" +walberla_module_file_name = prefix + "walberla_cpp" + suffix + +version = "${WALBERLA_VERSION}" if platform.system() == 'Windows': - extension = ('dll', 'pyd') configuration = 'Release' else: - extension = ('so', 'so') configuration = '' def collectFiles(): - src_shared_lib = join(walberla_binary_dir, configuration, 'walberla_cpp.' + extension[0]) - dst_shared_lib = join(walberla_binary_dir, 'waLBerla', 'walberla_cpp.' + extension[1]) + src_shared_lib = join(walberla_binary_dir, configuration, walberla_module_file_name) + dst_shared_lib = join(walberla_binary_dir, 'waLBerla', walberla_module_file_name) # copy everything inplace - print(src_shared_lib) - if not exists(src_shared_lib): print("Python Module was not built yet - run 'make walberla_cpp'") exit(1) @@ -43,17 +45,19 @@ packages = ['waLBerla', 'waLBerla.tools.report', 'waLBerla.tools.sqlitedb', 'waLBerla.tools.lbm_unitconversion', - 'waLBerla.tools.jobscripts'] + 'waLBerla.tools.jobscripts', + 'waLBerla.tools.config'] collectFiles() + setup(name='waLBerla', - version='1.0', - author='Martin Bauer', - author_email='martin.bauer@fau.de', + version=str(version), + author='Markus Holzer', + author_email='markus.holzer@fau.de', url='http://www.walberla.net', packages=packages, - package_data={'': ['walberla_cpp.' + extension[1]]} + package_data={'': [walberla_module_file_name]} ) if sys.argv[1] == 'build': diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/PythonExports.cpp b/apps/showcases/PhaseFieldAllenCahn/CPU/PythonExports.cpp index 40bb8c52b..4b73da880 100644 --- a/apps/showcases/PhaseFieldAllenCahn/CPU/PythonExports.cpp +++ b/apps/showcases/PhaseFieldAllenCahn/CPU/PythonExports.cpp @@ -20,68 +20,28 @@ #include "waLBerlaDefinitions.h" #ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/Manager.h" +# include "core/math/Constants.h" -#include "core/math/Constants.h" - -#include "blockforest/python/Exports.h" - -#include "field/communication/PackInfo.h" -#include "field/python/Exports.h" - -#include "geometry/python/Exports.h" - -#include "postprocessing/python/Exports.h" - -#include "timeloop/python/Exports.h" +# include "field/communication/PackInfo.h" +# include "python_coupling/Manager.h" +# include "python_coupling/export/BlockForestExport.h" +# include "python_coupling/export/FieldExports.h" namespace walberla { using flag_t = uint8_t; // clang-format off void exportDataStructuresToPython() { - namespace bmpl = boost::mpl; - auto pythonManager = python_coupling::Manager::instance(); - typedef bmpl::vector< - Field<walberla::real_t, 1>, - Field<walberla::real_t, 3>, - Field<walberla::real_t, 9>, - Field<walberla::real_t, 19>, - Field<walberla::real_t, 27>> - FieldTypes; - - typedef bmpl::vector<stencil::D2Q9, - stencil::D3Q19, - stencil::D3Q27> - Stencils; - - typedef bmpl::vector< - GhostLayerField<real_t,1>, - GhostLayerField<real_t,3>> - RealFieldTypes; - - typedef bmpl::vector< - FlagField<flag_t>> - FlagFieldTypes; // Field - pythonManager->addExporterFunction(field::exportModuleToPython<FieldTypes>); - pythonManager->addExporterFunction(field::exportGatherFunctions<FieldTypes>); - pythonManager->addBlockDataConversion<FieldTypes>(); + pythonManager->addExporterFunction(field::exportModuleToPython<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>); + pythonManager->addExporterFunction(field::exportGatherFunctions<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>); + pythonManager->addBlockDataConversion<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>(); // Blockforest - pythonManager->addExporterFunction(blockforest::exportModuleToPython<Stencils>); - - // Timeloop - pythonManager->addExporterFunction(timeloop::exportModuleToPython); - - // Postprocessing - pythonManager->addExporterFunction( postprocessing::exportModuleToPython<RealFieldTypes, FlagFieldTypes> ); - - // Geometry - pythonManager->addExporterFunction( geometry::exportModuleToPython ); + pythonManager->addExporterFunction(blockforest::exportModuleToPython<stencil::D2Q9, stencil::D3Q19, stencil::D3Q27>); } // clang-format on } diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp index cf1eff4d3..00f5fae4d 100644 --- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp +++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp @@ -28,13 +28,13 @@ #include "field/AddToStorage.h" #include "field/FlagField.h" #include "field/communication/PackInfo.h" -#include "field/python/Exports.h" #include "field/vtk/VTKWriter.h" #include "geometry/InitBoundaryHandling.h" #include "python_coupling/CreateConfig.h" #include "python_coupling/PythonCallback.h" +#include "python_coupling/export/FieldExports.h" #include "timeloop/SweepTimeloop.h" @@ -292,8 +292,8 @@ int main(int argc, char** argv) python_coupling::PythonCallback callback("at_end_of_time_step"); if (callback.isCallable()) { - callback.data().exposePtr("blocks", blocks); - callback.data().exposePtr("time_loop", timeLoop); + callback.data().exposeValue("blocks", blocks); + callback.data().exposeValue( "timeStep", timeLoop->getCurrentTimeStep()); callback(); } } diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py index 54f656cf9..1ef86f585 100755 --- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py +++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py @@ -13,7 +13,7 @@ class Scenario: self.dbWriteFrequency = 200 # simulation parameters - self.timesteps = 6001 + self.timesteps = 27001 self.cells = (64, 32, 64) self.blocks = (1, 8, 1) @@ -77,8 +77,8 @@ class Scenario: } @wlb.member_callback - def at_end_of_time_step(self, blocks, time_loop): - t = time_loop.getCurrentTimeStep() + def at_end_of_time_step(self, blocks, **kwargs): + t = kwargs['timeStep'] ny = self.size[1] l0 = self.size[0] if t % self.dbWriteFrequency == 0: @@ -88,22 +88,22 @@ class Scenario: mass = -100 spike_data = wlb.field.gather(blocks, 'phase', makeSlice[self.size[0] // 2, :, self.size[2] // 2]) if spike_data: - spike_field = np.asarray(spike_data.buffer()).squeeze() + spike_field = np.asarray(spike_data).squeeze() location_of_spike = (np.argmax(spike_field > 0.5) - ny // 2) / l0 bubble_data = wlb.field.gather(blocks, 'phase', makeSlice[0, :, 0]) if bubble_data: - bubble_field = np.asarray(bubble_data.buffer()).squeeze() + bubble_field = np.asarray(bubble_data).squeeze() location_of_bubble = (np.argmax(bubble_field > 0.5) - ny // 2) / l0 saddle_data = wlb.field.gather(blocks, 'phase', makeSlice[0, :, self.size[2] // 2]) if saddle_data: - saddle_field = np.asarray(saddle_data.buffer()).squeeze() + saddle_field = np.asarray(saddle_data).squeeze() location_of_saddle = (np.argmax(saddle_field > 0.5) - ny // 2) / l0 phase = wlb.field.gather(blocks, 'phase', makeSlice[:, :, :]) if phase: - phase_field = np.asarray(phase.buffer()).squeeze() + phase_field = np.asarray(phase).squeeze() mass = np.sum(phase_field) self.write_result_to_database(t, location_of_spike, location_of_bubble, location_of_saddle, mass) diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py index 9a8948868..d424d7c21 100755 --- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py +++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py @@ -91,12 +91,12 @@ class Scenario: } @wlb.member_callback - def at_end_of_time_step(self, blocks, time_loop): - t = time_loop.getCurrentTimeStep() + def at_end_of_time_step(self, blocks, **kwargs): + t = kwargs['timeStep'] if t % self.dbWriteFrequency == 0: wlb_field = wlb.field.gather(blocks, 'phase', makeSlice[:, :, self.size[2] // 2]) if wlb_field: - phase_field = np.asarray(wlb_field.buffer()).squeeze() + phase_field = np.asarray(wlb_field).squeeze() location_of_gas = np.where(phase_field < 0.5) cov = np.cov(location_of_gas) @@ -107,7 +107,9 @@ class Scenario: self.yPositions.append(center_of_mass[1]) if len(self.yPositions) > 1: speed = self.yPositions[-1] - self.yPositions[-2] - self.write_result_to_database(t, speed, axis_of_the_bubble, center_of_mass) + else: + speed = 0 + self.write_result_to_database(t, speed, axis_of_the_bubble, center_of_mass) self.counter += 1 diff --git a/apps/showcases/PhaseFieldAllenCahn/GPU/PythonExports.cpp b/apps/showcases/PhaseFieldAllenCahn/GPU/PythonExports.cpp index 40bb8c52b..4b73da880 100644 --- a/apps/showcases/PhaseFieldAllenCahn/GPU/PythonExports.cpp +++ b/apps/showcases/PhaseFieldAllenCahn/GPU/PythonExports.cpp @@ -20,68 +20,28 @@ #include "waLBerlaDefinitions.h" #ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/Manager.h" +# include "core/math/Constants.h" -#include "core/math/Constants.h" - -#include "blockforest/python/Exports.h" - -#include "field/communication/PackInfo.h" -#include "field/python/Exports.h" - -#include "geometry/python/Exports.h" - -#include "postprocessing/python/Exports.h" - -#include "timeloop/python/Exports.h" +# include "field/communication/PackInfo.h" +# include "python_coupling/Manager.h" +# include "python_coupling/export/BlockForestExport.h" +# include "python_coupling/export/FieldExports.h" namespace walberla { using flag_t = uint8_t; // clang-format off void exportDataStructuresToPython() { - namespace bmpl = boost::mpl; - auto pythonManager = python_coupling::Manager::instance(); - typedef bmpl::vector< - Field<walberla::real_t, 1>, - Field<walberla::real_t, 3>, - Field<walberla::real_t, 9>, - Field<walberla::real_t, 19>, - Field<walberla::real_t, 27>> - FieldTypes; - - typedef bmpl::vector<stencil::D2Q9, - stencil::D3Q19, - stencil::D3Q27> - Stencils; - - typedef bmpl::vector< - GhostLayerField<real_t,1>, - GhostLayerField<real_t,3>> - RealFieldTypes; - - typedef bmpl::vector< - FlagField<flag_t>> - FlagFieldTypes; // Field - pythonManager->addExporterFunction(field::exportModuleToPython<FieldTypes>); - pythonManager->addExporterFunction(field::exportGatherFunctions<FieldTypes>); - pythonManager->addBlockDataConversion<FieldTypes>(); + pythonManager->addExporterFunction(field::exportModuleToPython<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>); + pythonManager->addExporterFunction(field::exportGatherFunctions<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>); + pythonManager->addBlockDataConversion<Field<walberla::real_t, 1>, Field<walberla::real_t, 3>, Field<walberla::real_t, 9>, Field<walberla::real_t, 19>, Field<walberla::real_t, 27>>(); // Blockforest - pythonManager->addExporterFunction(blockforest::exportModuleToPython<Stencils>); - - // Timeloop - pythonManager->addExporterFunction(timeloop::exportModuleToPython); - - // Postprocessing - pythonManager->addExporterFunction( postprocessing::exportModuleToPython<RealFieldTypes, FlagFieldTypes> ); - - // Geometry - pythonManager->addExporterFunction( geometry::exportModuleToPython ); + pythonManager->addExporterFunction(blockforest::exportModuleToPython<stencil::D2Q9, stencil::D3Q19, stencil::D3Q27>); } // clang-format on } diff --git a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase.cpp b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase.cpp index a0029b540..2080ce38c 100644 --- a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase.cpp +++ b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase.cpp @@ -34,13 +34,13 @@ #include "field/AddToStorage.h" #include "field/FlagField.h" #include "field/communication/PackInfo.h" -#include "field/python/Exports.h" #include "field/vtk/VTKWriter.h" #include "geometry/InitBoundaryHandling.h" #include "python_coupling/CreateConfig.h" #include "python_coupling/PythonCallback.h" +#include "python_coupling/export/FieldExports.h" #include "timeloop/SweepTimeloop.h" @@ -319,8 +319,8 @@ int main(int argc, char** argv) python_coupling::PythonCallback callback("at_end_of_time_step"); if (callback.isCallable()) { - callback.data().exposePtr("blocks", blocks); - callback.data().exposePtr("time_loop", timeLoop); + callback.data().exposeValue("blocks", blocks); + callback.data().exposeValue( "timeStep", timeLoop->getCurrentTimeStep()); callback(); } } diff --git a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_RTI_3D.py b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_RTI_3D.py index d862b1a48..c9798504d 100755 --- a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_RTI_3D.py +++ b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_RTI_3D.py @@ -77,8 +77,8 @@ class Scenario: } @wlb.member_callback - def at_end_of_time_step(self, blocks, time_loop): - t = time_loop.getCurrentTimeStep() + def at_end_of_time_step(self, blocks, **kwargs): + t = kwargs['timeStep'] ny = self.size[1] l0 = self.size[0] if t % self.dbWriteFrequency == 0: @@ -88,22 +88,22 @@ class Scenario: mass = -100 spike_data = wlb.field.gather(blocks, 'phase', makeSlice[self.size[0] // 2, :, self.size[2] // 2]) if spike_data: - spike_field = np.asarray(spike_data.buffer()).squeeze() + spike_field = np.asarray(spike_data).squeeze() location_of_spike = (np.argmax(spike_field > 0.5) - ny // 2) / l0 bubble_data = wlb.field.gather(blocks, 'phase', makeSlice[0, :, 0]) if bubble_data: - bubble_field = np.asarray(bubble_data.buffer()).squeeze() + bubble_field = np.asarray(bubble_data).squeeze() location_of_bubble = (np.argmax(bubble_field > 0.5) - ny // 2) / l0 saddle_data = wlb.field.gather(blocks, 'phase', makeSlice[0, :, self.size[2] // 2]) if saddle_data: - saddle_field = np.asarray(saddle_data.buffer()).squeeze() + saddle_field = np.asarray(saddle_data).squeeze() location_of_saddle = (np.argmax(saddle_field > 0.5) - ny // 2) / l0 phase = wlb.field.gather(blocks, 'phase', makeSlice[:, :, :]) if phase: - phase_field = np.asarray(phase.buffer()).squeeze() + phase_field = np.asarray(phase).squeeze() mass = np.sum(phase_field) self.write_result_to_database(t, location_of_spike, location_of_bubble, location_of_saddle, mass) diff --git a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_rising_bubble.py b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_rising_bubble.py index 4294fc7b8..9f5f08323 100755 --- a/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_rising_bubble.py +++ b/apps/showcases/PhaseFieldAllenCahn/GPU/multiphase_rising_bubble.py @@ -91,12 +91,12 @@ class Scenario: } @wlb.member_callback - def at_end_of_time_step(self, blocks, time_loop): - t = time_loop.getCurrentTimeStep() + def at_end_of_time_step(self, blocks, **kwargs): + t = kwargs['timeStep'] if t % self.dbWriteFrequency == 0: wlb_field = wlb.field.gather(blocks, 'phase', makeSlice[:, :, self.size[2] // 2]) if wlb_field: - phase_field = np.asarray(wlb_field.buffer()).squeeze() + phase_field = np.asarray(wlb_field).squeeze() location_of_gas = np.where(phase_field < 0.5) cov = np.cov(location_of_gas) @@ -107,7 +107,9 @@ class Scenario: self.yPositions.append(center_of_mass[1]) if len(self.yPositions) > 1: speed = self.yPositions[-1] - self.yPositions[-2] - self.write_result_to_database(t, speed, axis_of_the_bubble, center_of_mass) + else: + speed = 0 + self.write_result_to_database(t, speed, axis_of_the_bubble, center_of_mass) self.counter += 1 diff --git a/cmake/FindBoost.cmake b/cmake/FindBoost.cmake deleted file mode 100644 index 4b16cf672..000000000 --- a/cmake/FindBoost.cmake +++ /dev/null @@ -1,2213 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindBoost ---------- - -Find Boost include dirs and libraries - -Use this module by invoking find_package with the form:: - - find_package(Boost - [version] [EXACT] # Minimum or EXACT version e.g. 1.67.0 - [REQUIRED] # Fail with error if Boost is not found - [COMPONENTS <libs>...] # Boost libraries by their canonical name - # e.g. "date_time" for "libboost_date_time" - [OPTIONAL_COMPONENTS <libs>...] - # Optional Boost libraries by their canonical name) - ) # e.g. "date_time" for "libboost_date_time" - -This module finds headers and requested component libraries OR a CMake -package configuration file provided by a "Boost CMake" build. For the -latter case skip to the "Boost CMake" section below. For the former -case results are reported in variables:: - - Boost_FOUND - True if headers and requested libraries were found - Boost_INCLUDE_DIRS - Boost include directories - Boost_LIBRARY_DIRS - Link directories for Boost libraries - Boost_LIBRARIES - Boost component libraries to be linked - Boost_<C>_FOUND - True if component <C> was found (<C> is upper-case) - Boost_<C>_LIBRARY - Libraries to link for component <C> (may include - target_link_libraries debug/optimized keywords) - Boost_VERSION - BOOST_VERSION value from boost/version.hpp - Boost_LIB_VERSION - Version string appended to library filenames - Boost_MAJOR_VERSION - Boost major version number (X in X.y.z) - Boost_MINOR_VERSION - Boost minor version number (Y in x.Y.z) - Boost_SUBMINOR_VERSION - Boost subminor version number (Z in x.y.Z) - Boost_VERSION_STRING - Boost version number in x.y.z format - Boost_LIB_DIAGNOSTIC_DEFINITIONS (Windows) - - Pass to add_definitions() to have diagnostic - information about Boost's automatic linking - displayed during compilation - -Note that Boost Python components require a Python version suffix -(Boost 1.67 and later), e.g. ``python36`` or ``python27`` for the -versions built against Python 3.6 and 2.7, respectively. This also -applies to additional components using Python including -``mpi_python`` and ``numpy``. Earlier Boost releases may use -distribution-specific suffixes such as ``2``, ``3`` or ``2.7``. -These may also be used as suffixes, but note that they are not -portable. - -This module reads hints about search locations from variables:: - - BOOST_ROOT - Preferred installation prefix - (or BOOSTROOT) - BOOST_INCLUDEDIR - Preferred include directory e.g. <prefix>/include - BOOST_LIBRARYDIR - Preferred library directory e.g. <prefix>/lib - Boost_NO_SYSTEM_PATHS - Set to ON to disable searching in locations not - specified by these hint variables. Default is OFF. - Boost_ADDITIONAL_VERSIONS - - List of Boost versions not known to this module - (Boost install locations may contain the version) - -and saves search results persistently in CMake cache entries:: - - Boost_INCLUDE_DIR - Directory containing Boost headers - Boost_LIBRARY_DIR_RELEASE - Directory containing release Boost libraries - Boost_LIBRARY_DIR_DEBUG - Directory containing debug Boost libraries - Boost_<C>_LIBRARY_DEBUG - Component <C> library debug variant - Boost_<C>_LIBRARY_RELEASE - Component <C> library release variant - -The following :prop_tgt:`IMPORTED` targets are also defined:: - - Boost::boost - Target for header-only dependencies - (Boost include directory) - Boost::<C> - Target for specific component dependency - (shared or static library); <C> is lower- - case - Boost::diagnostic_definitions - interface target to enable diagnostic - information about Boost's automatic linking - during compilation (adds BOOST_LIB_DIAGNOSTIC) - Boost::disable_autolinking - interface target to disable automatic - linking with MSVC (adds BOOST_ALL_NO_LIB) - Boost::dynamic_linking - interface target to enable dynamic linking - linking with MSVC (adds BOOST_ALL_DYN_LINK) - -Implicit dependencies such as Boost::filesystem requiring -Boost::system will be automatically detected and satisfied, even -if system is not specified when using find_package and if -Boost::system is not added to target_link_libraries. If using -Boost::thread, then Threads::Threads will also be added automatically. - -It is important to note that the imported targets behave differently -than variables created by this module: multiple calls to -find_package(Boost) in the same directory or sub-directories with -different options (e.g. static or shared) will not override the -values of the targets created by the first call. - -Users may set these hints or results as cache entries. Projects -should not read these entries directly but instead use the above -result variables. Note that some hint names start in upper-case -"BOOST". One may specify these as environment variables if they are -not specified as CMake variables or cache entries. - -This module first searches for the Boost header files using the above -hint variables (excluding BOOST_LIBRARYDIR) and saves the result in -Boost_INCLUDE_DIR. Then it searches for requested component libraries -using the above hints (excluding BOOST_INCLUDEDIR and -Boost_ADDITIONAL_VERSIONS), "lib" directories near Boost_INCLUDE_DIR, -and the library name configuration settings below. It saves the -library directories in Boost_LIBRARY_DIR_DEBUG and -Boost_LIBRARY_DIR_RELEASE and individual library -locations in Boost_<C>_LIBRARY_DEBUG and Boost_<C>_LIBRARY_RELEASE. -When one changes settings used by previous searches in the same build -tree (excluding environment variables) this module discards previous -search results affected by the changes and searches again. - -Boost libraries come in many variants encoded in their file name. -Users or projects may tell this module which variant to find by -setting variables:: - - Boost_USE_DEBUG_LIBS - Set to ON or OFF to specify whether to search - and use the debug libraries. Default is ON. - Boost_USE_RELEASE_LIBS - Set to ON or OFF to specify whether to search - and use the release libraries. Default is ON. - Boost_USE_MULTITHREADED - Set to OFF to use the non-multithreaded - libraries ('mt' tag). Default is ON. - Boost_USE_STATIC_LIBS - Set to ON to force the use of the static - libraries. Default is OFF. - Boost_USE_STATIC_RUNTIME - Set to ON or OFF to specify whether to use - libraries linked statically to the C++ runtime - ('s' tag). Default is platform dependent. - Boost_USE_DEBUG_RUNTIME - Set to ON or OFF to specify whether to use - libraries linked to the MS debug C++ runtime - ('g' tag). Default is ON. - Boost_USE_DEBUG_PYTHON - Set to ON to use libraries compiled with a - debug Python build ('y' tag). Default is OFF. - Boost_USE_STLPORT - Set to ON to use libraries compiled with - STLPort ('p' tag). Default is OFF. - Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS - - Set to ON to use libraries compiled with - STLPort deprecated "native iostreams" - ('n' tag). Default is OFF. - Boost_COMPILER - Set to the compiler-specific library suffix - (e.g. "-gcc43"). Default is auto-computed - for the C++ compiler in use. A list may be - used if multiple compatible suffixes should - be tested for, in decreasing order of - preference. - Boost_ARCHITECTURE - Set to the architecture-specific library suffix - (e.g. "-x64"). Default is auto-computed for the - C++ compiler in use. - Boost_THREADAPI - Suffix for "thread" component library name, - such as "pthread" or "win32". Names with - and without this suffix will both be tried. - Boost_NAMESPACE - Alternate namespace used to build boost with - e.g. if set to "myboost", will search for - myboost_thread instead of boost_thread. - -Other variables one may set to control this module are:: - - Boost_DEBUG - Set to ON to enable debug output from FindBoost. - Please enable this before filing any bug report. - Boost_DETAILED_FAILURE_MSG - - Set to ON to add detailed information to the - failure message even when the REQUIRED option - is not given to the find_package call. - Boost_REALPATH - Set to ON to resolve symlinks for discovered - libraries to assist with packaging. For example, - the "system" component library may be resolved to - "/usr/lib/libboost_system.so.1.67.0" instead of - "/usr/lib/libboost_system.so". This does not - affect linking and should not be enabled unless - the user needs this information. - Boost_LIBRARY_DIR - Default value for Boost_LIBRARY_DIR_RELEASE and - Boost_LIBRARY_DIR_DEBUG. - -On Visual Studio and Borland compilers Boost headers request automatic -linking to corresponding libraries. This requires matching libraries -to be linked explicitly or available in the link library search path. -In this case setting Boost_USE_STATIC_LIBS to OFF may not achieve -dynamic linking. Boost automatic linking typically requests static -libraries with a few exceptions (such as Boost.Python). Use:: - - add_definitions(${Boost_LIB_DIAGNOSTIC_DEFINITIONS}) - -to ask Boost to report information about automatic linking requests. - -Example to find Boost headers only:: - - find_package(Boost 1.36.0) - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - add_executable(foo foo.cc) - endif() - -Example to find Boost libraries and use imported targets:: - - find_package(Boost 1.56 REQUIRED COMPONENTS - date_time filesystem iostreams) - add_executable(foo foo.cc) - target_link_libraries(foo Boost::date_time Boost::filesystem - Boost::iostreams) - -Example to find Boost Python 3.6 libraries and use imported targets:: - - find_package(Boost 1.67 REQUIRED COMPONENTS - python36 numpy36) - add_executable(foo foo.cc) - target_link_libraries(foo Boost::python36 Boost::numpy36) - -Example to find Boost headers and some *static* (release only) libraries:: - - set(Boost_USE_STATIC_LIBS ON) # only find static libs - set(Boost_USE_DEBUG_LIBS OFF) # ignore debug libs and - set(Boost_USE_RELEASE_LIBS ON) # only find release libs - set(Boost_USE_MULTITHREADED ON) - set(Boost_USE_STATIC_RUNTIME OFF) - find_package(Boost 1.66.0 COMPONENTS date_time filesystem system ...) - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - add_executable(foo foo.cc) - target_link_libraries(foo ${Boost_LIBRARIES}) - endif() - -Boost CMake -^^^^^^^^^^^ - -If Boost was built using the boost-cmake project it provides a package -configuration file for use with find_package's Config mode. This -module looks for the package configuration file called -BoostConfig.cmake or boost-config.cmake and stores the result in cache -entry "Boost_DIR". If found, the package configuration file is loaded -and this module returns with no further action. See documentation of -the Boost CMake package configuration for details on what it provides. - -Set Boost_NO_BOOST_CMAKE to ON to disable the search for boost-cmake. -#]=======================================================================] - -# Save project's policies -cmake_policy(PUSH) -cmake_policy(SET CMP0057 NEW) # if IN_LIST - -#------------------------------------------------------------------------------- -# Before we go searching, check whether boost-cmake is available, unless the -# user specifically asked NOT to search for boost-cmake. -# -# If Boost_DIR is set, this behaves as any find_package call would. If not, -# it looks at BOOST_ROOT and BOOSTROOT to find Boost. -# -if (NOT Boost_NO_BOOST_CMAKE) - # If Boost_DIR is not set, look for BOOSTROOT and BOOST_ROOT as alternatives, - # since these are more conventional for Boost. - if ("$ENV{Boost_DIR}" STREQUAL "") - if (NOT "$ENV{BOOST_ROOT}" STREQUAL "") - set(ENV{Boost_DIR} $ENV{BOOST_ROOT}) - elseif (NOT "$ENV{BOOSTROOT}" STREQUAL "") - set(ENV{Boost_DIR} $ENV{BOOSTROOT}) - endif() - endif() - - # Do the same find_package call but look specifically for the CMake version. - # Note that args are passed in the Boost_FIND_xxxxx variables, so there is no - # need to delegate them to this find_package call. - find_package(Boost QUIET NO_MODULE) - mark_as_advanced(Boost_DIR) - - # If we found boost-cmake, then we're done. Print out what we found. - # Otherwise let the rest of the module try to find it. - if (Boost_FOUND) - message(STATUS "Boost ${Boost_FIND_VERSION} found.") - if (Boost_FIND_COMPONENTS) - message(STATUS "Found Boost components:\n ${Boost_FIND_COMPONENTS}") - endif() - # Restore project's policies - cmake_policy(POP) - return() - endif() -endif() - - -#------------------------------------------------------------------------------- -# FindBoost functions & macros -# - -############################################ -# -# Check the existence of the libraries. -# -############################################ -# This macro was taken directly from the FindQt4.cmake file that is included -# with the CMake distribution. This is NOT my work. All work was done by the -# original authors of the FindQt4.cmake file. Only minor modifications were -# made to remove references to Qt and make this file more generally applicable -# And ELSE/ENDIF pairs were removed for readability. -######################################################################### - -macro(_Boost_ADJUST_LIB_VARS basename) - if(Boost_INCLUDE_DIR ) - if(Boost_${basename}_LIBRARY_DEBUG AND Boost_${basename}_LIBRARY_RELEASE) - # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for - # single-config generators, set optimized and debug libraries - get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(_isMultiConfig OR CMAKE_BUILD_TYPE) - set(Boost_${basename}_LIBRARY optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) - else() - # For single-config generators where CMAKE_BUILD_TYPE has no value, - # just use the release libraries - set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) - endif() - # FIXME: This probably should be set for both cases - set(Boost_${basename}_LIBRARIES optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) - endif() - - # if only the release version was found, set the debug variable also to the release version - if(Boost_${basename}_LIBRARY_RELEASE AND NOT Boost_${basename}_LIBRARY_DEBUG) - set(Boost_${basename}_LIBRARY_DEBUG ${Boost_${basename}_LIBRARY_RELEASE}) - set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE}) - set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE}) - endif() - - # if only the debug version was found, set the release variable also to the debug version - if(Boost_${basename}_LIBRARY_DEBUG AND NOT Boost_${basename}_LIBRARY_RELEASE) - set(Boost_${basename}_LIBRARY_RELEASE ${Boost_${basename}_LIBRARY_DEBUG}) - set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_DEBUG}) - set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_DEBUG}) - endif() - - # If the debug & release library ends up being the same, omit the keywords - if("${Boost_${basename}_LIBRARY_RELEASE}" STREQUAL "${Boost_${basename}_LIBRARY_DEBUG}") - set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) - set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE} ) - endif() - - if(Boost_${basename}_LIBRARY AND Boost_${basename}_HEADER) - set(Boost_${basename}_FOUND ON) - if("x${basename}" STREQUAL "xTHREAD" AND NOT TARGET Threads::Threads) - string(APPEND Boost_ERROR_REASON_THREAD " (missing dependency: Threads)") - set(Boost_THREAD_FOUND OFF) - endif() - endif() - - endif() - # Make variables changeable to the advanced user - mark_as_advanced( - Boost_${basename}_LIBRARY_RELEASE - Boost_${basename}_LIBRARY_DEBUG - ) -endmacro() - -# Detect changes in used variables. -# Compares the current variable value with the last one. -# In short form: -# v != v_LAST -> CHANGED = 1 -# v is defined, v_LAST not -> CHANGED = 1 -# v is not defined, but v_LAST is -> CHANGED = 1 -# otherwise -> CHANGED = 0 -# CHANGED is returned in variable named ${changed_var} -macro(_Boost_CHANGE_DETECT changed_var) - set(${changed_var} 0) - foreach(v ${ARGN}) - if(DEFINED _Boost_COMPONENTS_SEARCHED) - if(${v}) - if(_${v}_LAST) - string(COMPARE NOTEQUAL "${${v}}" "${_${v}_LAST}" _${v}_CHANGED) - else() - set(_${v}_CHANGED 1) - endif() - elseif(_${v}_LAST) - set(_${v}_CHANGED 1) - endif() - if(_${v}_CHANGED) - set(${changed_var} 1) - endif() - else() - set(_${v}_CHANGED 0) - endif() - endforeach() -endmacro() - -# -# Find the given library (var). -# Use 'build_type' to support different lib paths for RELEASE or DEBUG builds -# -macro(_Boost_FIND_LIBRARY var build_type) - - find_library(${var} ${ARGN}) - - if(${var}) - # If this is the first library found then save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. - if(NOT Boost_LIBRARY_DIR_${build_type}) - get_filename_component(_dir "${${var}}" PATH) - set(Boost_LIBRARY_DIR_${build_type} "${_dir}" CACHE PATH "Boost library directory ${build_type}" FORCE) - endif() - elseif(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) - # Try component-specific hints but do not save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. - find_library(${var} HINTS ${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT} ${ARGN}) - endif() - - # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is known then search only there. - if(Boost_LIBRARY_DIR_${build_type}) - set(_boost_LIBRARY_SEARCH_DIRS_${build_type} ${Boost_LIBRARY_DIR_${build_type}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " Boost_LIBRARY_DIR_${build_type} = ${Boost_LIBRARY_DIR_${build_type}}" - " _boost_LIBRARY_SEARCH_DIRS_${build_type} = ${_boost_LIBRARY_SEARCH_DIRS_${build_type}}") - endif() - endif() -endmacro() - -#------------------------------------------------------------------------------- - -# Convert CMAKE_CXX_COMPILER_VERSION to boost compiler suffix version. -function(_Boost_COMPILER_DUMPVERSION _OUTPUT_VERSION _OUTPUT_VERSION_MAJOR _OUTPUT_VERSION_MINOR) - string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\1" - _boost_COMPILER_VERSION_MAJOR "${CMAKE_CXX_COMPILER_VERSION}") - string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\2" - _boost_COMPILER_VERSION_MINOR "${CMAKE_CXX_COMPILER_VERSION}") - - set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}${_boost_COMPILER_VERSION_MINOR}") - - set(${_OUTPUT_VERSION} ${_boost_COMPILER_VERSION} PARENT_SCOPE) - set(${_OUTPUT_VERSION_MAJOR} ${_boost_COMPILER_VERSION_MAJOR} PARENT_SCOPE) - set(${_OUTPUT_VERSION_MINOR} ${_boost_COMPILER_VERSION_MINOR} PARENT_SCOPE) -endfunction() - -# -# Take a list of libraries with "thread" in it -# and prepend duplicates with "thread_${Boost_THREADAPI}" -# at the front of the list -# -function(_Boost_PREPEND_LIST_WITH_THREADAPI _output) - set(_orig_libnames ${ARGN}) - string(REPLACE "thread" "thread_${Boost_THREADAPI}" _threadapi_libnames "${_orig_libnames}") - set(${_output} ${_threadapi_libnames} ${_orig_libnames} PARENT_SCOPE) -endfunction() - -# -# If a library is found, replace its cache entry with its REALPATH -# -function(_Boost_SWAP_WITH_REALPATH _library _docstring) - if(${_library}) - get_filename_component(_boost_filepathreal ${${_library}} REALPATH) - unset(${_library} CACHE) - set(${_library} ${_boost_filepathreal} CACHE FILEPATH "${_docstring}") - endif() -endfunction() - -function(_Boost_CHECK_SPELLING _var) - if(${_var}) - string(TOUPPER ${_var} _var_UC) - message(FATAL_ERROR "ERROR: ${_var} is not the correct spelling. The proper spelling is ${_var_UC}.") - endif() -endfunction() - -# Guesses Boost's compiler prefix used in built library names -# Returns the guess by setting the variable pointed to by _ret -function(_Boost_GUESS_COMPILER_PREFIX _ret) - if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") - if(WIN32) - set (_boost_COMPILER "-iw") - else() - set (_boost_COMPILER "-il") - endif() - elseif (GHSMULTI) - set(_boost_COMPILER "-ghs") - elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") - if(MSVC_TOOLSET_VERSION GREATER_EQUAL 141) - set(_boost_COMPILER "-vc141;-vc140") - elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 80) - set(_boost_COMPILER "-vc${MSVC_TOOLSET_VERSION}") - elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.10) - set(_boost_COMPILER "-vc71") - elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) # Good luck! - set(_boost_COMPILER "-vc7") # yes, this is correct - else() # VS 6.0 Good luck! - set(_boost_COMPILER "-vc6") # yes, this is correct - endif() - elseif (BORLAND) - set(_boost_COMPILER "-bcb") - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") - set(_boost_COMPILER "-sw") - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "XL") - set(_boost_COMPILER "-xlc") - elseif (MINGW) - if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) - set(_boost_COMPILER "-mgw") # no GCC version encoding prior to 1.34 - else() - _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION _boost_COMPILER_VERSION_MAJOR _boost_COMPILER_VERSION_MINOR) - set(_boost_COMPILER "-mgw${_boost_COMPILER_VERSION}") - endif() - elseif (UNIX) - _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION _boost_COMPILER_VERSION_MAJOR _boost_COMPILER_VERSION_MINOR) - if(NOT Boost_VERSION VERSION_LESS 106900) - # From GCC 5 and clang 4, versioning changes and minor becomes patch. - # For those compilers, patch is exclude from compiler tag in Boost 1.69+ library naming. - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND _boost_COMPILER_VERSION_MAJOR VERSION_GREATER 4) - set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}") - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND _boost_COMPILER_VERSION_MAJOR VERSION_GREATER 3) - set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}") - endif() - endif() - - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) - set(_boost_COMPILER "-gcc") # no GCC version encoding prior to 1.34 - else() - # Determine which version of GCC we have. - if(APPLE) - if(Boost_MINOR_VERSION) - if(${Boost_MINOR_VERSION} GREATER 35) - # In Boost 1.36.0 and newer, the mangled compiler name used - # on macOS/Darwin is "xgcc". - set(_boost_COMPILER "-xgcc${_boost_COMPILER_VERSION}") - else() - # In Boost <= 1.35.0, there is no mangled compiler name for - # the macOS/Darwin version of GCC. - set(_boost_COMPILER "") - endif() - else() - # We don't know the Boost version, so assume it's - # pre-1.36.0. - set(_boost_COMPILER "") - endif() - else() - set(_boost_COMPILER "-gcc${_boost_COMPILER_VERSION}") - endif() - endif() - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # TODO: Find out any Boost version constraints vs clang support. - set(_boost_COMPILER "-clang${_boost_COMPILER_VERSION}") - endif() - else() - # TODO at least Boost_DEBUG here? - set(_boost_COMPILER "") - endif() - set(${_ret} ${_boost_COMPILER} PARENT_SCOPE) -endfunction() - -# -# Get component dependencies. Requires the dependencies to have been -# defined for the Boost release version. -# -# component - the component to check -# _ret - list of library dependencies -# -function(_Boost_COMPONENT_DEPENDENCIES component _ret) - # Note: to add a new Boost release, run - # - # % cmake -DBOOST_DIR=/path/to/boost/source -P Utilities/Scripts/BoostScanDeps.cmake - # - # The output may be added in a new block below. If it's the same as - # the previous release, simply update the version range of the block - # for the previous release. Also check if any new components have - # been added, and add any new components to - # _Boost_COMPONENT_HEADERS. - # - # This information was originally generated by running - # BoostScanDeps.cmake against every boost release to date supported - # by FindBoost: - # - # % for version in /path/to/boost/sources/* - # do - # cmake -DBOOST_DIR=$version -P Utilities/Scripts/BoostScanDeps.cmake - # done - # - # The output was then updated by search and replace with these regexes: - # - # - Strip message(STATUS) prefix dashes - # s;^-- ;; - # - Indent - # s;^set(; set(;; - # - Add conditionals - # s;Scanning /path/to/boost/sources/boost_\(.*\)_\(.*\)_\(.*); elseif(NOT Boost_VERSION VERSION_LESS \10\20\3 AND Boost_VERSION VERSION_LESS xxxx); - # - # This results in the logic seen below, but will require the xxxx - # replacing with the following Boost release version (or the next - # minor version to be released, e.g. 1.59 was the latest at the time - # of writing, making 1.60 the next, so 106000 is the needed version - # number). Identical consecutive releases were then merged together - # by updating the end range of the first block and removing the - # following redundant blocks. - # - # Running the script against all historical releases should be - # required only if the BoostScanDeps.cmake script logic is changed. - # The addition of a new release should only require it to be run - # against the new release. - - # Handle Python version suffixes - if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") - set(component "${CMAKE_MATCH_1}") - set(component_python_version "${CMAKE_MATCH_2}") - endif() - - set(_Boost_IMPORTED_TARGETS TRUE) - if(Boost_VERSION AND Boost_VERSION VERSION_LESS 103300) - message(WARNING "Imported targets and dependency information not available for Boost version ${Boost_VERSION} (all versions older than 1.33)") - set(_Boost_IMPORTED_TARGETS FALSE) - elseif(NOT Boost_VERSION VERSION_LESS 103300 AND Boost_VERSION VERSION_LESS 103500) - set(_Boost_IOSTREAMS_DEPENDENCIES regex thread) - set(_Boost_REGEX_DEPENDENCIES thread) - set(_Boost_WAVE_DEPENDENCIES filesystem thread) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 103500 AND Boost_VERSION VERSION_LESS 103600) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_WAVE_DEPENDENCIES filesystem system thread) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 103600 AND Boost_VERSION VERSION_LESS 103800) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_WAVE_DEPENDENCIES filesystem system thread) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 103800 AND Boost_VERSION VERSION_LESS 104300) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 104300 AND Boost_VERSION VERSION_LESS 104400) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 104400 AND Boost_VERSION VERSION_LESS 104500) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random serialization) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_WAVE_DEPENDENCIES serialization filesystem system thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 104500 AND Boost_VERSION VERSION_LESS 104700) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 104700 AND Boost_VERSION VERSION_LESS 104800) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 104800 AND Boost_VERSION VERSION_LESS 105000) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES date_time) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105000 AND Boost_VERSION VERSION_LESS 105300) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105300 AND Boost_VERSION VERSION_LESS 105400) - set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105400 AND Boost_VERSION VERSION_LESS 105500) - set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105500 AND Boost_VERSION VERSION_LESS 105600) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105600 AND Boost_VERSION VERSION_LESS 105900) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 105900 AND Boost_VERSION VERSION_LESS 106000) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106000 AND Boost_VERSION VERSION_LESS 106100) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106100 AND Boost_VERSION VERSION_LESS 106200) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106200 AND Boost_VERSION VERSION_LESS 106300) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106300 AND Boost_VERSION VERSION_LESS 106500) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_COROUTINE2_DEPENDENCIES context fiber thread chrono system date_time) - set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106500 AND Boost_VERSION VERSION_LESS 106700) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106700 AND Boost_VERSION VERSION_LESS 106800) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106800 AND Boost_VERSION VERSION_LESS 106900) - set(_Boost_CHRONO_DEPENDENCIES system) - set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) - set(_Boost_CONTRACT_DEPENDENCIES thread chrono system date_time) - set(_Boost_COROUTINE_DEPENDENCIES context system) - set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) - set(_Boost_FILESYSTEM_DEPENDENCIES system) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_RANDOM_DEPENDENCIES system) - set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106900 AND Boost_VERSION VERSION_LESS 107000) - set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) - set(_Boost_COROUTINE_DEPENDENCIES context) - set(_Boost_FIBER_DEPENDENCIES context) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 107000 AND Boost_VERSION VERSION_LESS 107200) - set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) - set(_Boost_COROUTINE_DEPENDENCIES context) - set(_Boost_FIBER_DEPENDENCIES context) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono) - set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 107200 AND Boost_VERSION VERSION_LESS 107300) - set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) - set(_Boost_COROUTINE_DEPENDENCIES context) - set(_Boost_FIBER_DEPENDENCIES context) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l chrono atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono) - set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - else() - set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) - set(_Boost_COROUTINE_DEPENDENCIES context) - set(_Boost_FIBER_DEPENDENCIES context) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono) - set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - if(Boost_VERSION VERSION_GREATER_EQUAL 107400) - message(WARNING "New Boost version may have incorrect or missing dependencies and imported targets") - endif() - endif() - - - string(TOUPPER ${component} uppercomponent) - set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) - set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) - - string(REGEX REPLACE ";" " " _boost_DEPS_STRING "${_Boost_${uppercomponent}_DEPENDENCIES}") - if (NOT _boost_DEPS_STRING) - set(_boost_DEPS_STRING "(none)") - endif() - # message(STATUS "Dependencies for Boost::${component}: ${_boost_DEPS_STRING}") -endfunction() - -# -# Get component headers. This is the primary header (or headers) for -# a given component, and is used to check that the headers are present -# as well as the library itself as an extra sanity check of the build -# environment. -# -# component - the component to check -# _hdrs -# -function(_Boost_COMPONENT_HEADERS component _hdrs) - # Handle Python version suffixes - if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") - set(component "${CMAKE_MATCH_1}") - set(component_python_version "${CMAKE_MATCH_2}") - endif() - - # Note: new boost components will require adding here. The header - # must be present in all versions of Boost providing a library. - set(_Boost_ATOMIC_HEADERS "boost/atomic.hpp") - set(_Boost_CHRONO_HEADERS "boost/chrono.hpp") - set(_Boost_CONTAINER_HEADERS "boost/container/container_fwd.hpp") - set(_Boost_CONTRACT_HEADERS "boost/contract.hpp") - if(Boost_VERSION VERSION_LESS 106100) - set(_Boost_CONTEXT_HEADERS "boost/context/all.hpp") - else() - set(_Boost_CONTEXT_HEADERS "boost/context/detail/fcontext.hpp") - endif() - set(_Boost_COROUTINE_HEADERS "boost/coroutine/all.hpp") - set(_Boost_DATE_TIME_HEADERS "boost/date_time/date.hpp") - set(_Boost_EXCEPTION_HEADERS "boost/exception/exception.hpp") - set(_Boost_FIBER_HEADERS "boost/fiber/all.hpp") - set(_Boost_FILESYSTEM_HEADERS "boost/filesystem/path.hpp") - set(_Boost_GRAPH_HEADERS "boost/graph/adjacency_list.hpp") - set(_Boost_GRAPH_PARALLEL_HEADERS "boost/graph/adjacency_list.hpp") - set(_Boost_IOSTREAMS_HEADERS "boost/iostreams/stream.hpp") - set(_Boost_LOCALE_HEADERS "boost/locale.hpp") - set(_Boost_LOG_HEADERS "boost/log/core.hpp") - set(_Boost_LOG_SETUP_HEADERS "boost/log/detail/setup_config.hpp") - set(_Boost_MATH_HEADERS "boost/math_fwd.hpp") - set(_Boost_MATH_C99_HEADERS "boost/math/tr1.hpp") - set(_Boost_MATH_C99F_HEADERS "boost/math/tr1.hpp") - set(_Boost_MATH_C99L_HEADERS "boost/math/tr1.hpp") - set(_Boost_MATH_TR1_HEADERS "boost/math/tr1.hpp") - set(_Boost_MATH_TR1F_HEADERS "boost/math/tr1.hpp") - set(_Boost_MATH_TR1L_HEADERS "boost/math/tr1.hpp") - set(_Boost_MPI_HEADERS "boost/mpi.hpp") - set(_Boost_MPI_PYTHON_HEADERS "boost/mpi/python/config.hpp") - set(_Boost_NUMPY_HEADERS "boost/python/numpy.hpp") - set(_Boost_PRG_EXEC_MONITOR_HEADERS "boost/test/prg_exec_monitor.hpp") - set(_Boost_PROGRAM_OPTIONS_HEADERS "boost/program_options.hpp") - set(_Boost_PYTHON_HEADERS "boost/python.hpp") - set(_Boost_RANDOM_HEADERS "boost/random.hpp") - set(_Boost_REGEX_HEADERS "boost/regex.hpp") - set(_Boost_SERIALIZATION_HEADERS "boost/serialization/serialization.hpp") - set(_Boost_SIGNALS_HEADERS "boost/signals.hpp") - set(_Boost_STACKTRACE_ADDR2LINE_HEADERS "boost/stacktrace.hpp") - set(_Boost_STACKTRACE_BACKTRACE_HEADERS "boost/stacktrace.hpp") - set(_Boost_STACKTRACE_BASIC_HEADERS "boost/stacktrace.hpp") - set(_Boost_STACKTRACE_NOOP_HEADERS "boost/stacktrace.hpp") - set(_Boost_STACKTRACE_WINDBG_CACHED_HEADERS "boost/stacktrace.hpp") - set(_Boost_STACKTRACE_WINDBG_HEADERS "boost/stacktrace.hpp") - set(_Boost_SYSTEM_HEADERS "boost/system/config.hpp") - set(_Boost_TEST_EXEC_MONITOR_HEADERS "boost/test/test_exec_monitor.hpp") - set(_Boost_THREAD_HEADERS "boost/thread.hpp") - set(_Boost_TIMER_HEADERS "boost/timer.hpp") - set(_Boost_TYPE_ERASURE_HEADERS "boost/type_erasure/config.hpp") - set(_Boost_UNIT_TEST_FRAMEWORK_HEADERS "boost/test/framework.hpp") - set(_Boost_WAVE_HEADERS "boost/wave.hpp") - set(_Boost_WSERIALIZATION_HEADERS "boost/archive/text_wiarchive.hpp") - if(WIN32) - set(_Boost_BZIP2_HEADERS "boost/iostreams/filter/bzip2.hpp") - set(_Boost_ZLIB_HEADERS "boost/iostreams/filter/zlib.hpp") - endif() - - string(TOUPPER ${component} uppercomponent) - set(${_hdrs} ${_Boost_${uppercomponent}_HEADERS} PARENT_SCOPE) - - string(REGEX REPLACE ";" " " _boost_HDRS_STRING "${_Boost_${uppercomponent}_HEADERS}") - if (NOT _boost_HDRS_STRING) - set(_boost_HDRS_STRING "(none)") - endif() - # message(STATUS "Headers for Boost::${component}: ${_boost_HDRS_STRING}") -endfunction() - -# -# Determine if any missing dependencies require adding to the component list. -# -# Sets _Boost_${COMPONENT}_DEPENDENCIES for each required component, -# plus _Boost_IMPORTED_TARGETS (TRUE if imported targets should be -# defined; FALSE if dependency information is unavailable). -# -# componentvar - the component list variable name -# extravar - the indirect dependency list variable name -# -# -function(_Boost_MISSING_DEPENDENCIES componentvar extravar) - # _boost_unprocessed_components - list of components requiring processing - # _boost_processed_components - components already processed (or currently being processed) - # _boost_new_components - new components discovered for future processing - # - list(APPEND _boost_unprocessed_components ${${componentvar}}) - - while(_boost_unprocessed_components) - list(APPEND _boost_processed_components ${_boost_unprocessed_components}) - foreach(component ${_boost_unprocessed_components}) - string(TOUPPER ${component} uppercomponent) - set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) - _Boost_COMPONENT_DEPENDENCIES("${component}" _Boost_${uppercomponent}_DEPENDENCIES) - set(_Boost_${uppercomponent}_DEPENDENCIES ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) - set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) - foreach(componentdep ${_Boost_${uppercomponent}_DEPENDENCIES}) - if (NOT ("${componentdep}" IN_LIST _boost_processed_components OR "${componentdep}" IN_LIST _boost_new_components)) - list(APPEND _boost_new_components ${componentdep}) - endif() - endforeach() - endforeach() - set(_boost_unprocessed_components ${_boost_new_components}) - unset(_boost_new_components) - endwhile() - set(_boost_extra_components ${_boost_processed_components}) - if(_boost_extra_components AND ${componentvar}) - list(REMOVE_ITEM _boost_extra_components ${${componentvar}}) - endif() - set(${componentvar} ${_boost_processed_components} PARENT_SCOPE) - set(${extravar} ${_boost_extra_components} PARENT_SCOPE) -endfunction() - -# -# Some boost libraries may require particular set of compler features. -# The very first one was `boost::fiber` introduced in Boost 1.62. -# One can check required compiler features of it in -# `${Boost_ROOT}/libs/fiber/build/Jamfile.v2`. -# -function(_Boost_COMPILER_FEATURES component _ret) - # Boost >= 1.62 and < 1.67 - if(NOT Boost_VERSION VERSION_LESS 106200 AND Boost_VERSION VERSION_LESS 106700) - set(_Boost_FIBER_COMPILER_FEATURES - cxx_alias_templates - cxx_auto_type - cxx_constexpr - cxx_defaulted_functions - cxx_final - cxx_lambdas - cxx_noexcept - cxx_nullptr - cxx_rvalue_references - cxx_thread_local - cxx_variadic_templates - ) - endif() - string(TOUPPER ${component} uppercomponent) - set(${_ret} ${_Boost_${uppercomponent}_COMPILER_FEATURES} PARENT_SCOPE) -endfunction() - -# -# Update library search directory hint variable with paths used by prebuilt boost binaries. -# -# Prebuilt windows binaries (https://sourceforge.net/projects/boost/files/boost-binaries/) -# have library directories named using MSVC compiler version and architecture. -# This function would append corresponding directories if MSVC is a current compiler, -# so having `BOOST_ROOT` would be enough to specify to find everything. -# -function(_Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS componentlibvar basedir) - if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(_arch_suffix 64) - else() - set(_arch_suffix 32) - endif() - if(MSVC_TOOLSET_VERSION GREATER_EQUAL 141) - list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.1) - list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.0) - elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 80) - math(EXPR _toolset_major_version "${MSVC_TOOLSET_VERSION} / 10") - list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-${_toolset_major_version}.0) - endif() - set(${componentlibvar} ${${componentlibvar}} PARENT_SCOPE) - endif() -endfunction() - -# -# End functions/macros -# -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# main. -#------------------------------------------------------------------------------- - - -# If the user sets Boost_LIBRARY_DIR, use it as the default for both -# configurations. -if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR) - set(Boost_LIBRARY_DIR_RELEASE "${Boost_LIBRARY_DIR}") -endif() -if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR) - set(Boost_LIBRARY_DIR_DEBUG "${Boost_LIBRARY_DIR}") -endif() - -if(NOT DEFINED Boost_USE_DEBUG_LIBS) - set(Boost_USE_DEBUG_LIBS TRUE) -endif() -if(NOT DEFINED Boost_USE_RELEASE_LIBS) - set(Boost_USE_RELEASE_LIBS TRUE) -endif() -if(NOT DEFINED Boost_USE_MULTITHREADED) - set(Boost_USE_MULTITHREADED TRUE) -endif() -if(NOT DEFINED Boost_USE_DEBUG_RUNTIME) - set(Boost_USE_DEBUG_RUNTIME TRUE) -endif() - -# Check the version of Boost against the requested version. -if(Boost_FIND_VERSION AND NOT Boost_FIND_VERSION_MINOR) - message(SEND_ERROR "When requesting a specific version of Boost, you must provide at least the major and minor version numbers, e.g., 1.34") -endif() - -if(Boost_FIND_VERSION_EXACT) - # The version may appear in a directory with or without the patch - # level, even when the patch level is non-zero. - set(_boost_TEST_VERSIONS - "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}.${Boost_FIND_VERSION_PATCH}" - "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") -else() - # The user has not requested an exact version. Among known - # versions, find those that are acceptable to the user request. - # - # Note: When adding a new Boost release, also update the dependency - # information in _Boost_COMPONENT_DEPENDENCIES and - # _Boost_COMPONENT_HEADERS. See the instructions at the top of - # _Boost_COMPONENT_DEPENDENCIES. - set(_Boost_KNOWN_VERSIONS ${Boost_ADDITIONAL_VERSIONS} - "1.70.0" "1.70" "1.69.0" "1.69" - "1.68.0" "1.68" "1.67.0" "1.67" "1.66.0" "1.66" "1.65.1" "1.65.0" "1.65" - "1.64.0" "1.64" "1.63.0" "1.63" "1.62.0" "1.62" "1.61.0" "1.61" "1.60.0" "1.60" - "1.59.0" "1.59" "1.58.0" "1.58" "1.57.0" "1.57" "1.56.0" "1.56" "1.55.0" "1.55" - "1.54.0" "1.54" "1.53.0" "1.53" "1.52.0" "1.52" "1.51.0" "1.51" - "1.50.0" "1.50" "1.49.0" "1.49" "1.48.0" "1.48" "1.47.0" "1.47" "1.46.1" - "1.46.0" "1.46" "1.45.0" "1.45" "1.44.0" "1.44" "1.43.0" "1.43" "1.42.0" "1.42" - "1.41.0" "1.41" "1.40.0" "1.40" "1.39.0" "1.39" "1.38.0" "1.38" "1.37.0" "1.37" - "1.36.1" "1.36.0" "1.36" "1.35.1" "1.35.0" "1.35" "1.34.1" "1.34.0" - "1.34" "1.33.1" "1.33.0" "1.33") - - set(_boost_TEST_VERSIONS) - if(Boost_FIND_VERSION) - set(_Boost_FIND_VERSION_SHORT "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") - # Select acceptable versions. - foreach(version ${_Boost_KNOWN_VERSIONS}) - if(NOT "${version}" VERSION_LESS "${Boost_FIND_VERSION}") - # This version is high enough. - list(APPEND _boost_TEST_VERSIONS "${version}") - elseif("${version}.99" VERSION_EQUAL "${_Boost_FIND_VERSION_SHORT}.99") - # This version is a short-form for the requested version with - # the patch level dropped. - list(APPEND _boost_TEST_VERSIONS "${version}") - endif() - endforeach() - else() - # Any version is acceptable. - set(_boost_TEST_VERSIONS "${_Boost_KNOWN_VERSIONS}") - endif() -endif() - -# The reason that we failed to find Boost. This will be set to a -# user-friendly message when we fail to find some necessary piece of -# Boost. -set(Boost_ERROR_REASON) - -if(Boost_DEBUG) - # Output some of their choices - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_USE_MULTITHREADED = ${Boost_USE_MULTITHREADED}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_USE_STATIC_LIBS = ${Boost_USE_STATIC_LIBS}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_USE_STATIC_RUNTIME = ${Boost_USE_STATIC_RUNTIME}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_ADDITIONAL_VERSIONS = ${Boost_ADDITIONAL_VERSIONS}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_NO_SYSTEM_PATHS = ${Boost_NO_SYSTEM_PATHS}") -endif() - -# Supply Boost_LIB_DIAGNOSTIC_DEFINITIONS as a convenience target. It -# will only contain any interface definitions on WIN32, but is created -# on all platforms to keep end user code free from platform dependent -# code. Also provide convenience targets to disable autolinking and -# enable dynamic linking. -if(NOT TARGET Boost::diagnostic_definitions) - add_library(Boost::diagnostic_definitions INTERFACE IMPORTED) - add_library(Boost::disable_autolinking INTERFACE IMPORTED) - add_library(Boost::dynamic_linking INTERFACE IMPORTED) - set_target_properties(Boost::dynamic_linking PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_DYN_LINK") -endif() -if(WIN32) - # In windows, automatic linking is performed, so you do not have - # to specify the libraries. If you are linking to a dynamic - # runtime, then you can choose to link to either a static or a - # dynamic Boost library, the default is to do a static link. You - # can alter this for a specific library "whatever" by defining - # BOOST_WHATEVER_DYN_LINK to force Boost library "whatever" to be - # linked dynamically. Alternatively you can force all Boost - # libraries to dynamic link by defining BOOST_ALL_DYN_LINK. - - # This feature can be disabled for Boost library "whatever" by - # defining BOOST_WHATEVER_NO_LIB, or for all of Boost by defining - # BOOST_ALL_NO_LIB. - - # If you want to observe which libraries are being linked against - # then defining BOOST_LIB_DIAGNOSTIC will cause the auto-linking - # code to emit a #pragma message each time a library is selected - # for linking. - set(Boost_LIB_DIAGNOSTIC_DEFINITIONS "-DBOOST_LIB_DIAGNOSTIC") - set_target_properties(Boost::diagnostic_definitions PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "BOOST_LIB_DIAGNOSTIC") - set_target_properties(Boost::disable_autolinking PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") -endif() - -_Boost_CHECK_SPELLING(Boost_ROOT) -_Boost_CHECK_SPELLING(Boost_LIBRARYDIR) -_Boost_CHECK_SPELLING(Boost_INCLUDEDIR) - -# Collect environment variable inputs as hints. Do not consider changes. -foreach(v BOOSTROOT BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR) - set(_env $ENV{${v}}) - if(_env) - file(TO_CMAKE_PATH "${_env}" _ENV_${v}) - else() - set(_ENV_${v} "") - endif() -endforeach() -if(NOT _ENV_BOOST_ROOT AND _ENV_BOOSTROOT) - set(_ENV_BOOST_ROOT "${_ENV_BOOSTROOT}") -endif() - -# Collect inputs and cached results. Detect changes since the last run. -if(NOT BOOST_ROOT AND BOOSTROOT) - set(BOOST_ROOT "${BOOSTROOT}") -endif() -set(_Boost_VARS_DIR - BOOST_ROOT - Boost_NO_SYSTEM_PATHS - ) - -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Declared as CMake or Environmental Variables:") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " BOOST_ROOT = ${BOOST_ROOT}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " BOOST_INCLUDEDIR = ${BOOST_INCLUDEDIR}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " BOOST_LIBRARYDIR = ${BOOST_LIBRARYDIR}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") -endif() - -# ------------------------------------------------------------------------ -# Search for Boost include DIR -# ------------------------------------------------------------------------ - -set(_Boost_VARS_INC BOOST_INCLUDEDIR Boost_INCLUDE_DIR Boost_ADDITIONAL_VERSIONS) -_Boost_CHANGE_DETECT(_Boost_CHANGE_INCDIR ${_Boost_VARS_DIR} ${_Boost_VARS_INC}) -# Clear Boost_INCLUDE_DIR if it did not change but other input affecting the -# location did. We will find a new one based on the new inputs. -if(_Boost_CHANGE_INCDIR AND NOT _Boost_INCLUDE_DIR_CHANGED) - unset(Boost_INCLUDE_DIR CACHE) -endif() - -if(NOT Boost_INCLUDE_DIR) - set(_boost_INCLUDE_SEARCH_DIRS "") - if(BOOST_INCLUDEDIR) - list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_INCLUDEDIR}) - elseif(_ENV_BOOST_INCLUDEDIR) - list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_INCLUDEDIR}) - endif() - - if( BOOST_ROOT ) - list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_ROOT}/include ${BOOST_ROOT}) - elseif( _ENV_BOOST_ROOT ) - list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_ROOT}/include ${_ENV_BOOST_ROOT}) - endif() - - if( Boost_NO_SYSTEM_PATHS) - list(APPEND _boost_INCLUDE_SEARCH_DIRS NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) - else() - if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") - foreach(ver ${_boost_TEST_VERSIONS}) - string(REPLACE "." "_" ver "${ver}") - list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS "C:/local/boost_${ver}") - endforeach() - endif() - list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS - C:/boost/include - C:/boost - /sw/local/include - ) - endif() - - # Try to find Boost by stepping backwards through the Boost versions - # we know about. - # Build a list of path suffixes for each version. - set(_boost_PATH_SUFFIXES) - foreach(_boost_VER ${_boost_TEST_VERSIONS}) - # Add in a path suffix, based on the required version, ideally - # we could read this from version.hpp, but for that to work we'd - # need to know the include dir already - set(_boost_BOOSTIFIED_VERSION) - - # Transform 1.35 => 1_35 and 1.36.0 => 1_36_0 - if(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") - set(_boost_BOOSTIFIED_VERSION - "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}_${CMAKE_MATCH_3}") - elseif(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)") - set(_boost_BOOSTIFIED_VERSION - "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}") - endif() - - list(APPEND _boost_PATH_SUFFIXES - "boost-${_boost_BOOSTIFIED_VERSION}" - "boost_${_boost_BOOSTIFIED_VERSION}" - "boost/boost-${_boost_BOOSTIFIED_VERSION}" - "boost/boost_${_boost_BOOSTIFIED_VERSION}" - ) - - endforeach() - - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Include debugging info:") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " _boost_INCLUDE_SEARCH_DIRS = ${_boost_INCLUDE_SEARCH_DIRS}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - " _boost_PATH_SUFFIXES = ${_boost_PATH_SUFFIXES}") - endif() - - # Look for a standard boost header file. - find_path(Boost_INCLUDE_DIR - NAMES boost/config.hpp - HINTS ${_boost_INCLUDE_SEARCH_DIRS} - PATH_SUFFIXES ${_boost_PATH_SUFFIXES} - ) -endif() - -# ------------------------------------------------------------------------ -# Extract version information from version.hpp -# ------------------------------------------------------------------------ - -# Set Boost_FOUND based only on header location and version. -# It will be updated below for component libraries. -if(Boost_INCLUDE_DIR) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "location of version.hpp: ${Boost_INCLUDE_DIR}/boost/version.hpp") - endif() - - # Extract Boost_VERSION and Boost_LIB_VERSION from version.hpp - set(Boost_VERSION 0) - set(Boost_LIB_VERSION "") - file(STRINGS "${Boost_INCLUDE_DIR}/boost/version.hpp" _boost_VERSION_HPP_CONTENTS REGEX "#define BOOST_(LIB_)?VERSION ") - set(_Boost_VERSION_REGEX "([0-9]+)") - set(_Boost_LIB_VERSION_REGEX "\"([0-9_]+)\"") - foreach(v VERSION LIB_VERSION) - if("${_boost_VERSION_HPP_CONTENTS}" MATCHES "#define BOOST_${v} ${_Boost_${v}_REGEX}") - set(Boost_${v} "${CMAKE_MATCH_1}") - endif() - endforeach() - unset(_boost_VERSION_HPP_CONTENTS) - - math(EXPR Boost_MAJOR_VERSION "${Boost_VERSION} / 100000") - math(EXPR Boost_MINOR_VERSION "${Boost_VERSION} / 100 % 1000") - math(EXPR Boost_SUBMINOR_VERSION "${Boost_VERSION} % 100") - set(Boost_VERSION_STRING "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") - - string(APPEND Boost_ERROR_REASON - "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}\nBoost include path: ${Boost_INCLUDE_DIR}") - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "version.hpp reveals boost " - "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") - endif() - - if(Boost_FIND_VERSION) - # Set Boost_FOUND based on requested version. - set(_Boost_VERSION "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") - if("${_Boost_VERSION}" VERSION_LESS "${Boost_FIND_VERSION}") - set(Boost_FOUND 0) - set(_Boost_VERSION_AGE "old") - elseif(Boost_FIND_VERSION_EXACT AND - NOT "${_Boost_VERSION}" VERSION_EQUAL "${Boost_FIND_VERSION}") - set(Boost_FOUND 0) - set(_Boost_VERSION_AGE "new") - else() - set(Boost_FOUND 1) - endif() - if(NOT Boost_FOUND) - # State that we found a version of Boost that is too new or too old. - string(APPEND Boost_ERROR_REASON - "\nDetected version of Boost is too ${_Boost_VERSION_AGE}. Requested version was ${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") - if (Boost_FIND_VERSION_PATCH) - string(APPEND Boost_ERROR_REASON - ".${Boost_FIND_VERSION_PATCH}") - endif () - if (NOT Boost_FIND_VERSION_EXACT) - string(APPEND Boost_ERROR_REASON " (or newer)") - endif () - string(APPEND Boost_ERROR_REASON ".") - endif () - else() - # Caller will accept any Boost version. - set(Boost_FOUND 1) - endif() -else() - set(Boost_FOUND 0) - string(APPEND Boost_ERROR_REASON - "Unable to find the Boost header files. Please set BOOST_ROOT to the root directory containing Boost or BOOST_INCLUDEDIR to the directory containing Boost's headers.") -endif() - -# ------------------------------------------------------------------------ -# Prefix initialization -# ------------------------------------------------------------------------ - -set(Boost_LIB_PREFIX "") -if ( (GHSMULTI AND Boost_USE_STATIC_LIBS) OR - (WIN32 AND Boost_USE_STATIC_LIBS AND NOT CYGWIN) ) - set(Boost_LIB_PREFIX "lib") -endif() - -if ( NOT Boost_NAMESPACE ) - set(Boost_NAMESPACE "boost") -endif() - -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_LIB_PREFIX = ${Boost_LIB_PREFIX}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_NAMESPACE = ${Boost_NAMESPACE}") -endif() - -# ------------------------------------------------------------------------ -# Suffix initialization and compiler suffix detection. -# ------------------------------------------------------------------------ - -set(_Boost_VARS_NAME - Boost_NAMESPACE - Boost_COMPILER - Boost_THREADAPI - Boost_USE_DEBUG_PYTHON - Boost_USE_MULTITHREADED - Boost_USE_STATIC_LIBS - Boost_USE_STATIC_RUNTIME - Boost_USE_STLPORT - Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS - ) -_Boost_CHANGE_DETECT(_Boost_CHANGE_LIBNAME ${_Boost_VARS_NAME}) - -# Setting some more suffixes for the library -if (Boost_COMPILER) - set(_boost_COMPILER ${Boost_COMPILER}) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "using user-specified Boost_COMPILER = ${_boost_COMPILER}") - endif() -else() - # Attempt to guess the compiler suffix - # NOTE: this is not perfect yet, if you experience any issues - # please report them and use the Boost_COMPILER variable - # to work around the problems. - _Boost_GUESS_COMPILER_PREFIX(_boost_COMPILER) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "guessed _boost_COMPILER = ${_boost_COMPILER}") - endif() -endif() - -set (_boost_MULTITHREADED "-mt") -if( NOT Boost_USE_MULTITHREADED ) - set (_boost_MULTITHREADED "") -endif() -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_MULTITHREADED = ${_boost_MULTITHREADED}") -endif() - -#====================== -# Systematically build up the Boost ABI tag for the 'tagged' and 'versioned' layouts -# http://boost.org/doc/libs/1_66_0/more/getting_started/windows.html#library-naming -# http://boost.org/doc/libs/1_66_0/boost/config/auto_link.hpp -# http://boost.org/doc/libs/1_66_0/tools/build/src/tools/common.jam -# http://boost.org/doc/libs/1_66_0/boostcpp.jam -set( _boost_RELEASE_ABI_TAG "-") -set( _boost_DEBUG_ABI_TAG "-") -# Key Use this library when: -# s linking statically to the C++ standard library and -# compiler runtime support libraries. -if(Boost_USE_STATIC_RUNTIME) - set( _boost_RELEASE_ABI_TAG "${_boost_RELEASE_ABI_TAG}s") - set( _boost_DEBUG_ABI_TAG "${_boost_DEBUG_ABI_TAG}s") -endif() -# g using debug versions of the standard and runtime -# support libraries -if(WIN32 AND Boost_USE_DEBUG_RUNTIME) - if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC" - OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang" - OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") - string(APPEND _boost_DEBUG_ABI_TAG "g") - endif() -endif() -# y using special debug build of python -if(Boost_USE_DEBUG_PYTHON) - string(APPEND _boost_DEBUG_ABI_TAG "y") -endif() -# d using a debug version of your code -string(APPEND _boost_DEBUG_ABI_TAG "d") -# p using the STLport standard library rather than the -# default one supplied with your compiler -if(Boost_USE_STLPORT) - string(APPEND _boost_RELEASE_ABI_TAG "p") - string(APPEND _boost_DEBUG_ABI_TAG "p") -endif() -# n using the STLport deprecated "native iostreams" feature -# removed from the documentation in 1.43.0 but still present in -# boost/config/auto_link.hpp -if(Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS) - string(APPEND _boost_RELEASE_ABI_TAG "n") - string(APPEND _boost_DEBUG_ABI_TAG "n") -endif() - -# -x86 Architecture and address model tag -# First character is the architecture, then word-size, either 32 or 64 -# Only used in 'versioned' layout, added in Boost 1.66.0 -if(DEFINED Boost_ARCHITECTURE) - set(_boost_ARCHITECTURE_TAG "${Boost_ARCHITECTURE}") - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "using user-specified Boost_ARCHITECTURE = ${_boost_ARCHITECTURE_TAG}") - endif() -else() - set(_boost_ARCHITECTURE_TAG "") - # {CMAKE_CXX_COMPILER_ARCHITECTURE_ID} is not currently set for all compilers - if(NOT "x${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "x" AND NOT Boost_VERSION VERSION_LESS 106600) - string(APPEND _boost_ARCHITECTURE_TAG "-") - # This needs to be kept in-sync with the section of CMakePlatformId.h.in - # inside 'defined(_WIN32) && defined(_MSC_VER)' - if(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "IA64") - string(APPEND _boost_ARCHITECTURE_TAG "i") - elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "X86" - OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64") - string(APPEND _boost_ARCHITECTURE_TAG "x") - elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "^ARM") - string(APPEND _boost_ARCHITECTURE_TAG "a") - elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "MIPS") - string(APPEND _boost_ARCHITECTURE_TAG "m") - endif() - - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - string(APPEND _boost_ARCHITECTURE_TAG "64") - else() - string(APPEND _boost_ARCHITECTURE_TAG "32") - endif() - endif() -endif() - -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_RELEASE_ABI_TAG = ${_boost_RELEASE_ABI_TAG}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_DEBUG_ABI_TAG = ${_boost_DEBUG_ABI_TAG}") -endif() - -# ------------------------------------------------------------------------ -# Begin finding boost libraries -# ------------------------------------------------------------------------ - -set(_Boost_VARS_LIB "") -foreach(c DEBUG RELEASE) - set(_Boost_VARS_LIB_${c} BOOST_LIBRARYDIR Boost_LIBRARY_DIR_${c}) - list(APPEND _Boost_VARS_LIB ${_Boost_VARS_LIB_${c}}) - _Boost_CHANGE_DETECT(_Boost_CHANGE_LIBDIR_${c} ${_Boost_VARS_DIR} ${_Boost_VARS_LIB_${c}} Boost_INCLUDE_DIR) - # Clear Boost_LIBRARY_DIR_${c} if it did not change but other input affecting the - # location did. We will find a new one based on the new inputs. - if(_Boost_CHANGE_LIBDIR_${c} AND NOT _Boost_LIBRARY_DIR_${c}_CHANGED) - unset(Boost_LIBRARY_DIR_${c} CACHE) - endif() - - # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is set, prefer its value. - if(Boost_LIBRARY_DIR_${c}) - set(_boost_LIBRARY_SEARCH_DIRS_${c} ${Boost_LIBRARY_DIR_${c}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) - else() - set(_boost_LIBRARY_SEARCH_DIRS_${c} "") - if(BOOST_LIBRARYDIR) - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_LIBRARYDIR}) - elseif(_ENV_BOOST_LIBRARYDIR) - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_LIBRARYDIR}) - endif() - - if(BOOST_ROOT) - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_ROOT}/lib ${BOOST_ROOT}/stage/lib) - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${BOOST_ROOT}") - elseif(_ENV_BOOST_ROOT) - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_ROOT}/lib ${_ENV_BOOST_ROOT}/stage/lib) - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${_ENV_BOOST_ROOT}") - endif() - - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} - ${Boost_INCLUDE_DIR}/lib - ${Boost_INCLUDE_DIR}/../lib - ${Boost_INCLUDE_DIR}/stage/lib - ) - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}/..") - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}") - if( Boost_NO_SYSTEM_PATHS ) - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) - else() - foreach(ver ${_boost_TEST_VERSIONS}) - string(REPLACE "." "_" ver "${ver}") - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/local/boost_${ver}") - endforeach() - _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/boost") - list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} PATHS - C:/boost/lib - C:/boost - /sw/local/lib - ) - endif() - endif() -endforeach() - -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "_boost_LIBRARY_SEARCH_DIRS_RELEASE = ${_boost_LIBRARY_SEARCH_DIRS_RELEASE}" - "_boost_LIBRARY_SEARCH_DIRS_DEBUG = ${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") -endif() - -# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES -if( Boost_USE_STATIC_LIBS ) - set( _boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - if(WIN32) - list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 .lib .a) - else() - set(CMAKE_FIND_LIBRARY_SUFFIXES .a) - endif() -endif() - -# We want to use the tag inline below without risking double dashes -if(_boost_RELEASE_ABI_TAG) - if(${_boost_RELEASE_ABI_TAG} STREQUAL "-") - set(_boost_RELEASE_ABI_TAG "") - endif() -endif() -if(_boost_DEBUG_ABI_TAG) - if(${_boost_DEBUG_ABI_TAG} STREQUAL "-") - set(_boost_DEBUG_ABI_TAG "") - endif() -endif() - -# The previous behavior of FindBoost when Boost_USE_STATIC_LIBS was enabled -# on WIN32 was to: -# 1. Search for static libs compiled against a SHARED C++ standard runtime library (use if found) -# 2. Search for static libs compiled against a STATIC C++ standard runtime library (use if found) -# We maintain this behavior since changing it could break people's builds. -# To disable the ambiguous behavior, the user need only -# set Boost_USE_STATIC_RUNTIME either ON or OFF. -set(_boost_STATIC_RUNTIME_WORKAROUND false) -if(WIN32 AND Boost_USE_STATIC_LIBS) - if(NOT DEFINED Boost_USE_STATIC_RUNTIME) - set(_boost_STATIC_RUNTIME_WORKAROUND TRUE) - endif() -endif() - -# On versions < 1.35, remove the System library from the considered list -# since it wasn't added until 1.35. -if(Boost_VERSION AND Boost_FIND_COMPONENTS) - if(Boost_VERSION LESS 103500) - list(REMOVE_ITEM Boost_FIND_COMPONENTS system) - endif() -endif() - -# Additional components may be required via component dependencies. -# Add any missing components to the list. -_Boost_MISSING_DEPENDENCIES(Boost_FIND_COMPONENTS _Boost_EXTRA_FIND_COMPONENTS) - -# If thread is required, get the thread libs as a dependency -if("thread" IN_LIST Boost_FIND_COMPONENTS) - if(Boost_FIND_QUIETLY) - set(_Boost_find_quiet QUIET) - else() - set(_Boost_find_quiet "") - endif() - find_package(Threads ${_Boost_find_quiet}) - unset(_Boost_find_quiet) -endif() - -# If the user changed any of our control inputs flush previous results. -if(_Boost_CHANGE_LIBDIR_DEBUG OR _Boost_CHANGE_LIBDIR_RELEASE OR _Boost_CHANGE_LIBNAME) - foreach(COMPONENT ${_Boost_COMPONENTS_SEARCHED}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - foreach(c DEBUG RELEASE) - set(_var Boost_${UPPERCOMPONENT}_LIBRARY_${c}) - unset(${_var} CACHE) - set(${_var} "${_var}-NOTFOUND") - endforeach() - endforeach() - set(_Boost_COMPONENTS_SEARCHED "") -endif() - -foreach(COMPONENT ${Boost_FIND_COMPONENTS}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - - set( _boost_docstring_release "Boost ${COMPONENT} library (release)") - set( _boost_docstring_debug "Boost ${COMPONENT} library (debug)") - - # Compute component-specific hints. - set(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT "") - if(${COMPONENT} STREQUAL "mpi" OR ${COMPONENT} STREQUAL "mpi_python" OR - ${COMPONENT} STREQUAL "graph_parallel") - foreach(lib ${MPI_CXX_LIBRARIES} ${MPI_C_LIBRARIES}) - if(IS_ABSOLUTE "${lib}") - get_filename_component(libdir "${lib}" PATH) - string(REPLACE "\\" "/" libdir "${libdir}") - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT ${libdir}) - endif() - endforeach() - endif() - - # Handle Python version suffixes - unset(COMPONENT_PYTHON_VERSION_MAJOR) - unset(COMPONENT_PYTHON_VERSION_MINOR) - if(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\$") - set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") - set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") - elseif(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\\.?([0-9])\$") - set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") - set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") - set(COMPONENT_PYTHON_VERSION_MINOR "${CMAKE_MATCH_3}") - endif() - - unset(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) - if (COMPONENT_PYTHON_VERSION_MINOR) - # Boost >= 1.67 - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") - # Debian/Ubuntu (Some versions omit the 2 and/or 3 from the suffix) - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") - # Gentoo - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") - # RPMs - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") - endif() - if (COMPONENT_PYTHON_VERSION_MAJOR AND NOT COMPONENT_PYTHON_VERSION_MINOR) - # Boost < 1.67 - list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}") - endif() - - # Consolidate and report component-specific hints. - if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) - list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Component-specific library search names for ${COMPONENT_NAME}: " - "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME}") - endif() - endif() - if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) - list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Component-specific library search paths for ${COMPONENT}: " - "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT}") - endif() - endif() - - # - # Find headers - # - _Boost_COMPONENT_HEADERS("${COMPONENT}" Boost_${UPPERCOMPONENT}_HEADER_NAME) - # Look for a standard boost header file. - if(Boost_${UPPERCOMPONENT}_HEADER_NAME) - if(EXISTS "${Boost_INCLUDE_DIR}/${Boost_${UPPERCOMPONENT}_HEADER_NAME}") - set(Boost_${UPPERCOMPONENT}_HEADER ON) - else() - set(Boost_${UPPERCOMPONENT}_HEADER OFF) - endif() - else() - set(Boost_${UPPERCOMPONENT}_HEADER ON) - message(WARNING "No header defined for ${COMPONENT}; skipping header check") - endif() - - # - # Find RELEASE libraries - # - unset(_boost_RELEASE_NAMES) - foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) - foreach(compiler IN LISTS _boost_COMPILER) - list(APPEND _boost_RELEASE_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} ) - endforeach() - list(APPEND _boost_RELEASE_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) - if(_boost_STATIC_RUNTIME_WORKAROUND) - set(_boost_RELEASE_STATIC_ABI_TAG "-s${_boost_RELEASE_ABI_TAG}") - foreach(compiler IN LISTS _boost_COMPILER) - list(APPEND _boost_RELEASE_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) - endforeach() - list(APPEND _boost_RELEASE_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) - endif() - endforeach() - if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") - _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_RELEASE_NAMES ${_boost_RELEASE_NAMES}) - endif() - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Searching for ${UPPERCOMPONENT}_LIBRARY_RELEASE: ${_boost_RELEASE_NAMES}") - endif() - - # if Boost_LIBRARY_DIR_RELEASE is not defined, - # but Boost_LIBRARY_DIR_DEBUG is, look there first for RELEASE libs - if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR_DEBUG) - list(INSERT _boost_LIBRARY_SEARCH_DIRS_RELEASE 0 ${Boost_LIBRARY_DIR_DEBUG}) - endif() - - # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. - string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_RELEASE}") - - if(Boost_USE_RELEASE_LIBS) - _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE RELEASE - NAMES ${_boost_RELEASE_NAMES} - HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} - NAMES_PER_DIR - DOC "${_boost_docstring_release}" - ) - endif() - - # - # Find DEBUG libraries - # - unset(_boost_DEBUG_NAMES) - foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) - foreach(compiler IN LISTS _boost_COMPILER) - list(APPEND _boost_DEBUG_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} ) - endforeach() - list(APPEND _boost_DEBUG_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) - if(_boost_STATIC_RUNTIME_WORKAROUND) - set(_boost_DEBUG_STATIC_ABI_TAG "-s${_boost_DEBUG_ABI_TAG}") - foreach(compiler IN LISTS _boost_COMPILER) - list(APPEND _boost_DEBUG_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) - endforeach() - list(APPEND _boost_DEBUG_NAMES - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} - ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) - endif() - endforeach() - if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") - _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_DEBUG_NAMES ${_boost_DEBUG_NAMES}) - endif() - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Searching for ${UPPERCOMPONENT}_LIBRARY_DEBUG: ${_boost_DEBUG_NAMES}") - endif() - - # if Boost_LIBRARY_DIR_DEBUG is not defined, - # but Boost_LIBRARY_DIR_RELEASE is, look there first for DEBUG libs - if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR_RELEASE) - list(INSERT _boost_LIBRARY_SEARCH_DIRS_DEBUG 0 ${Boost_LIBRARY_DIR_RELEASE}) - endif() - - # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. - string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") - - if(Boost_USE_DEBUG_LIBS) - _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG DEBUG - NAMES ${_boost_DEBUG_NAMES} - HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} - NAMES_PER_DIR - DOC "${_boost_docstring_debug}" - ) - endif () - - if(Boost_REALPATH) - _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE "${_boost_docstring_release}") - _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG "${_boost_docstring_debug}" ) - endif() - - _Boost_ADJUST_LIB_VARS(${UPPERCOMPONENT}) - - # Check if component requires some compiler features - _Boost_COMPILER_FEATURES(${COMPONENT} _Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) - -endforeach() - -# Restore the original find library ordering -if( Boost_USE_STATIC_LIBS ) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${_boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) -endif() - -# ------------------------------------------------------------------------ -# End finding boost libraries -# ------------------------------------------------------------------------ - -set(Boost_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) -set(Boost_LIBRARY_DIRS) -if(Boost_LIBRARY_DIR_RELEASE) - list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_RELEASE}) -endif() -if(Boost_LIBRARY_DIR_DEBUG) - list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_DEBUG}) -endif() -if(Boost_LIBRARY_DIRS) - list(REMOVE_DUPLICATES Boost_LIBRARY_DIRS) -endif() - -# The above setting of Boost_FOUND was based only on the header files. -# Update it for the requested component libraries. -if(Boost_FOUND) - # The headers were found. Check for requested component libs. - set(_boost_CHECKED_COMPONENT FALSE) - set(_Boost_MISSING_COMPONENTS "") - foreach(COMPONENT ${Boost_FIND_COMPONENTS}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - set(_boost_CHECKED_COMPONENT TRUE) - if(NOT Boost_${UPPERCOMPONENT}_FOUND AND Boost_FIND_REQUIRED_${COMPONENT}) - list(APPEND _Boost_MISSING_COMPONENTS ${COMPONENT}) - endif() - endforeach() - if(_Boost_MISSING_COMPONENTS AND _Boost_EXTRA_FIND_COMPONENTS) - # Optional indirect dependencies are not counted as missing. - list(REMOVE_ITEM _Boost_MISSING_COMPONENTS ${_Boost_EXTRA_FIND_COMPONENTS}) - endif() - - if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] Boost_FOUND = ${Boost_FOUND}") - endif() - - if (_Boost_MISSING_COMPONENTS) - set(Boost_FOUND 0) - # We were unable to find some libraries, so generate a sensible - # error message that lists the libraries we were unable to find. - string(APPEND Boost_ERROR_REASON - "\nCould not find the following") - if(Boost_USE_STATIC_LIBS) - string(APPEND Boost_ERROR_REASON " static") - endif() - string(APPEND Boost_ERROR_REASON - " Boost libraries:\n") - foreach(COMPONENT ${_Boost_MISSING_COMPONENTS}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - string(APPEND Boost_ERROR_REASON - " ${Boost_NAMESPACE}_${COMPONENT}${Boost_ERROR_REASON_${UPPERCOMPONENT}}\n") - endforeach() - - list(LENGTH Boost_FIND_COMPONENTS Boost_NUM_COMPONENTS_WANTED) - list(LENGTH _Boost_MISSING_COMPONENTS Boost_NUM_MISSING_COMPONENTS) - if (${Boost_NUM_COMPONENTS_WANTED} EQUAL ${Boost_NUM_MISSING_COMPONENTS}) - string(APPEND Boost_ERROR_REASON - "No Boost libraries were found. You may need to set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") - else () - string(APPEND Boost_ERROR_REASON - "Some (but not all) of the required Boost libraries were found. You may need to install these additional Boost libraries. Alternatively, set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") - endif () - endif () - - if( NOT Boost_LIBRARY_DIRS AND NOT _boost_CHECKED_COMPONENT ) - # Compatibility Code for backwards compatibility with CMake - # 2.4's FindBoost module. - - # Look for the boost library path. - # Note that the user may not have installed any libraries - # so it is quite possible the Boost_LIBRARY_DIRS may not exist. - set(_boost_LIB_DIR ${Boost_INCLUDE_DIR}) - - if("${_boost_LIB_DIR}" MATCHES "boost-[0-9]+") - get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) - endif() - - if("${_boost_LIB_DIR}" MATCHES "/include$") - # Strip off the trailing "/include" in the path. - get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) - endif() - - if(EXISTS "${_boost_LIB_DIR}/lib") - string(APPEND _boost_LIB_DIR /lib) - elseif(EXISTS "${_boost_LIB_DIR}/stage/lib") - string(APPEND _boost_LIB_DIR "/stage/lib") - else() - set(_boost_LIB_DIR "") - endif() - - if(_boost_LIB_DIR AND EXISTS "${_boost_LIB_DIR}") - set(Boost_LIBRARY_DIRS ${_boost_LIB_DIR}) - endif() - - endif() -else() - # Boost headers were not found so no components were found. - foreach(COMPONENT ${Boost_FIND_COMPONENTS}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - set(Boost_${UPPERCOMPONENT}_FOUND 0) - endforeach() -endif() - -# ------------------------------------------------------------------------ -# Add imported targets -# ------------------------------------------------------------------------ - -if(Boost_FOUND) - # For header-only libraries - if(NOT TARGET Boost::boost) - add_library(Boost::boost INTERFACE IMPORTED) - if(Boost_INCLUDE_DIRS) - set_target_properties(Boost::boost PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") - endif() - endif() - - foreach(COMPONENT ${Boost_FIND_COMPONENTS}) - if(_Boost_IMPORTED_TARGETS AND NOT TARGET Boost::${COMPONENT}) - string(TOUPPER ${COMPONENT} UPPERCOMPONENT) - if(Boost_${UPPERCOMPONENT}_FOUND) - if(Boost_USE_STATIC_LIBS) - add_library(Boost::${COMPONENT} STATIC IMPORTED) - else() - # Even if Boost_USE_STATIC_LIBS is OFF, we might have static - # libraries as a result. - add_library(Boost::${COMPONENT} UNKNOWN IMPORTED) - endif() - if(Boost_INCLUDE_DIRS) - set_target_properties(Boost::${COMPONENT} PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") - endif() - if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY}") - set_target_properties(Boost::${COMPONENT} PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${Boost_${UPPERCOMPONENT}_LIBRARY}") - endif() - if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") - set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(Boost::${COMPONENT} PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" - IMPORTED_LOCATION_RELEASE "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") - endif() - if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") - set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(Boost::${COMPONENT} PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX" - IMPORTED_LOCATION_DEBUG "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") - endif() - if(_Boost_${UPPERCOMPONENT}_DEPENDENCIES) - unset(_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES) - foreach(dep ${_Boost_${UPPERCOMPONENT}_DEPENDENCIES}) - list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Boost::${dep}) - endforeach() - if(COMPONENT STREQUAL "thread") - list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Threads::Threads) - endif() - set_target_properties(Boost::${COMPONENT} PROPERTIES - INTERFACE_LINK_LIBRARIES "${_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES}") - endif() - if(_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) - set_target_properties(Boost::${COMPONENT} PROPERTIES - INTERFACE_COMPILE_FEATURES "${_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES}") - endif() - endif() - endif() - endforeach() -endif() - -# ------------------------------------------------------------------------ -# Notification to end user about what was found -# ------------------------------------------------------------------------ - -set(Boost_LIBRARIES "") -if(Boost_FOUND) - if(NOT Boost_FIND_QUIETLY) - message(STATUS "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") - if(Boost_FIND_COMPONENTS) - message(STATUS "Found the following Boost libraries:") - endif() - endif() - foreach( COMPONENT ${Boost_FIND_COMPONENTS} ) - string( TOUPPER ${COMPONENT} UPPERCOMPONENT ) - if( Boost_${UPPERCOMPONENT}_FOUND ) - if(NOT Boost_FIND_QUIETLY) - message (STATUS " ${COMPONENT}") - endif() - list(APPEND Boost_LIBRARIES ${Boost_${UPPERCOMPONENT}_LIBRARY}) - if(COMPONENT STREQUAL "thread") - list(APPEND Boost_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) - endif() - endif() - endforeach() -else() - if(Boost_FIND_REQUIRED) - message(SEND_ERROR "Unable to find the requested Boost libraries.\n${Boost_ERROR_REASON}") - else() - if(NOT Boost_FIND_QUIETLY) - # we opt not to automatically output Boost_ERROR_REASON here as - # it could be quite lengthy and somewhat imposing in its requests - # Since Boost is not always a required dependency we'll leave this - # up to the end-user. - if(Boost_DEBUG OR Boost_DETAILED_FAILURE_MSG) - message(STATUS "Could NOT find Boost\n${Boost_ERROR_REASON}") - else() - message(STATUS "Could NOT find Boost") - endif() - endif() - endif() -endif() - -# Configure display of cache entries in GUI. -foreach(v BOOSTROOT BOOST_ROOT ${_Boost_VARS_INC} ${_Boost_VARS_LIB}) - get_property(_type CACHE ${v} PROPERTY TYPE) - if(_type) - set_property(CACHE ${v} PROPERTY ADVANCED 1) - if("x${_type}" STREQUAL "xUNINITIALIZED") - if("x${v}" STREQUAL "xBoost_ADDITIONAL_VERSIONS") - set_property(CACHE ${v} PROPERTY TYPE STRING) - else() - set_property(CACHE ${v} PROPERTY TYPE PATH) - endif() - endif() - endif() -endforeach() - -# Record last used values of input variables so we can -# detect on the next run if the user changed them. -foreach(v - ${_Boost_VARS_INC} ${_Boost_VARS_LIB} - ${_Boost_VARS_DIR} ${_Boost_VARS_NAME} - ) - if(DEFINED ${v}) - set(_${v}_LAST "${${v}}" CACHE INTERNAL "Last used ${v} value.") - else() - unset(_${v}_LAST CACHE) - endif() -endforeach() - -# Maintain a persistent list of components requested anywhere since -# the last flush. -set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}") -list(APPEND _Boost_COMPONENTS_SEARCHED ${Boost_FIND_COMPONENTS}) -list(REMOVE_DUPLICATES _Boost_COMPONENTS_SEARCHED) -list(SORT _Boost_COMPONENTS_SEARCHED) -set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}" - CACHE INTERNAL "Components requested for this build tree.") - -# Restore project's policies -cmake_policy(POP) diff --git a/doc/setup.dox b/doc/setup.dox index 5368c1e5e..e740b0e04 100644 --- a/doc/setup.dox +++ b/doc/setup.dox @@ -73,8 +73,7 @@ WALBERLA_BUILD_WITH_MPI | ON | Since one main goal of waLBerla WALBERLA_BUILD_WITH_OPENMP | OFF | Enables/Disables OpenMP support for thread-parallel computation. Can also be combined with MPI for hybrid simulations! WALBERLA_BUILD_TESTS | OFF | If enabled, all tests are built when running make in the root build folder. But you can always go to a specific directory in your test folder and manually run make. WALBERLA_BUILD_BENCHMARKS | ON | Enables/Disables the automatic build of all benchmarks located in "apps/benchmarks". -WALBERLA_BUILD_WITH_PYTHON | OFF | Enables Python Support inside waLBerla (embedded Python). Then you can use Python scripts as configuration files and start an embedded python interpreter that can access waLBerla data structures. -WALBERLA_BUILD_WITH_PYTHON_MODULE | OFF | This builds a shared library (and python module) walberla.so in "apps/pythonmodule" so that you can use walberla from python. +WALBERLA_BUILD_WITH_PYTHON | OFF | Enables Python Support inside waLBerla (embedded Python). Then you can use Python scripts as configuration files and start an embedded python interpreter that can access waLBerla data structures. This builds a shared library (and python module) walberla_cpp.so in "apps/pythonmodule" so that you can use walberla from python. For a list of all switches, see CMakeLists.txt in the root source folder. @@ -87,40 +86,13 @@ e.g. geometry setup, post-processing or steering. This mode can be enabled by th While in this first option the driving code is C++, the second option exports most of waLBerlas functionality as a Python module that can be imported from a regular Python script. Thus the application itself can be written in Python. -To build and install the waLBerla Python module enable WALBERLA_BUILD_WITH_PYTHON and WALBERLA_BUILD_WITH_PYTHON_MODULE. +To build and install the waLBerla Python module enable WALBERLA_BUILD_WITH_PYTHON. Then run 'make pythonModule' to build and 'make pythonModuleInstall' to install the module in your current environment. +The python coupling is build up on <a target="_blank" href="https://pybind11.readthedocs.io/en/stable/index.html">pybind11</a> entirely. pybind11 +provides a lightweight header only solution for that. Thus there is no need to install additional software since pybind11 +is shipped with waLBerla as a submodule. The integration happens automatically when waLBerla is build with python. -The third option is code generation using 'pystencils' and 'lbmpy' and is covered later. - -\subsection python_boost Installing Python dependencies. - -Both variants described above introduce dependencies on the Python and the boost::python library. We support building -against Python in version 3.3 and above. On Ubuntu install 'python3-dev' and 'libboost-python-dev'. Then CMake -automatically finds the right libraries. - -On clusters the setup process can be more complicated. Usually boost has to be compiled manually there. One very common -problem then is to not link boost::python against the same python lib as waLBerla is linked against. For example when boost -python is built against Python2 and waLBerla linked against Python3, linker errors occur. You can test what your -boost python was linked against with 'grep -rl "PyString_Type" libbboost_python'. If something is found, it was built -against Python2, if not, against Python3. - -To build boost correctly against a certain Python version do: -\verbatim -./bootstrap.sh --with-python=/path/to/python3 --with-python-root=/base/dir/with/lib/and/include/ --with-python-version=3.Xm -\endverbatim - -To be on the safe side then edit the project-config.jam and adapt the following line (here for example python3.6m) -by entering the location of the interpreter, include path and lib path: -\verbatim -using python : 3.6 : /path/to/python3 : /python/root/include/python3.6m/ : /python/root/lib/ ; -\endverbatim - -Then build with -\verbatim -./b2 -j $NUM_PROCS toolset=$TOOLSET install --prefix=$PREFIX -\endverbatim - -Don't forget to set BOOST_ROOT such that CMake finds your custom boost library. +The third option is code generation using <a target="_blank" href="https://pycodegen.pages.i10git.cs.fau.de/pystencils/">pystencils</a> and <a href="https://pycodegen.pages.i10git.cs.fau.de/lbmpy" target="_blank">lbmpy</a> and is covered in the tutorial section of waLBerla. \section ide_setup Setup of Integrated Development Environments diff --git a/extern/pybind11 b/extern/pybind11 new file mode 160000 index 000000000..526a7733c --- /dev/null +++ b/extern/pybind11 @@ -0,0 +1 @@ +Subproject commit 526a7733c773016f2e552777612027954d0765fd diff --git a/python/pystencils_walberla/boundary.py b/python/pystencils_walberla/boundary.py index 7c7547001..73d429ec5 100644 --- a/python/pystencils_walberla/boundary.py +++ b/python/pystencils_walberla/boundary.py @@ -1,5 +1,3 @@ -from functools import partial - import numpy as np from jinja2 import Environment, PackageLoader, StrictUndefined from pystencils import Field, FieldType diff --git a/python/waLBerla/__init__.py b/python/waLBerla/__init__.py index db838d835..6e1e3ebab 100644 --- a/python/waLBerla/__init__.py +++ b/python/waLBerla/__init__.py @@ -4,14 +4,13 @@ from .callbacks import memberCallback as member_callback # noqa:F401 import sys + try: from .walberla_cpp import * # noqa: F403 - cpp_available = True except ImportError: try: from walberla_cpp import * # noqa: F403 - cpp_available = True except ImportError: cpp_available = False @@ -41,26 +40,14 @@ if cpp_available: sys.modules[__name__ + '.field'] = field # noqa: F405 # extend the C++ module with some python functions from .field_extension import extend as extend_field - extend_field(field) # noqa: F405 + if 'cuda' in globals(): sys.modules[__name__ + '.cuda'] = cuda # noqa: F405 from .cuda_extension import extend as extend_cuda - extend_cuda(cuda) # noqa: F405 - if 'geometry' in globals(): - sys.modules[__name__ + '.geometry'] = geometry # noqa: F405 - if 'lbm' in globals(): - sys.modules[__name__ + '.lbm'] = lbm # noqa: F405 - if 'postprocessing' in globals(): - sys.modules[__name__ + 'postprocessing'] = postprocessing # noqa: F405 if 'mpi' in globals(): sys.modules[__name__ + '.mpi'] = mpi # noqa: F405 - if 'timeloop' in globals(): - sys.modules[__name__ + '.timeloop'] = timeloop # noqa: F405 - from .timeloop_extension import extend as extend_timeloop - - extend_timeloop(timeloop) # noqa: F405 else: class Dummy: pass diff --git a/python/waLBerla/callbacks.py b/python/waLBerla/callbacks.py index 8b24c6e0e..f9153ec5b 100644 --- a/python/waLBerla/callbacks.py +++ b/python/waLBerla/callbacks.py @@ -45,7 +45,6 @@ To register a certain python function as callback it has to be set as attribute """ from __future__ import print_function, absolute_import, division, unicode_literals -import os from functools import partial @@ -54,10 +53,10 @@ from functools import partial class callback: """Decorator class to mark a Python function as waLBerla callback""" - def __init__(self, callbackFunction): - if not type(callbackFunction) is str: + def __init__(self, callback_function): + if not type(callback_function) is str: raise Exception("waLBerla callback: Name of function has to be a string") - self.callbackFunction = callbackFunction + self.callbackFunction = callback_function def __call__(self, f): try: @@ -94,7 +93,6 @@ class ScenarioManager: Activation means to register the _configLoopCallback as 'config' waLBerla callback function which is called when a new scenario is expected. When config is called again the calbacks of the next scenario are activated. - """ def __init__(self): @@ -110,19 +108,19 @@ class ScenarioManager: """Activates this scenario manager instance""" try: import walberla_cpp - boundFunc = self._configLoopCallback - setattr(walberla_cpp.callbacks, "config", boundFunc) + bound_func = self._configLoopCallback + setattr(walberla_cpp.callbacks, "config", bound_func) except ImportError: pass - def restrictScenarios(self, startScenario=0): + def restrictScenarios(self, start_scenario=0): """Simulates not all scenarios registered at this manager, but skips the first startScenario-1 scenarios""" - self._startScenario = startScenario + self._startScenario = start_scenario def _configLoopCallback(self, *args, **kwargs): - def findCallbacks(classType): + def findCallbacks(class_type): res = dict() - for key, value in classType.__dict__.items(): + for key, value in class_type.__dict__.items(): if hasattr(value, "waLBerla_callback_member"): res[key] = value return res @@ -145,24 +143,14 @@ class ScenarioManager: try: import walberla_cpp - if 'WALBERLA_SCENARIO_IDX' in os.environ: - scenario_idx = int(os.environ['WALBERLA_SCENARIO_IDX']) - try: - scenario = self._scenarios[scenario_idx] - except IndexError: - walberla_cpp.log_info_on_root("Scenario does not exists - all scenarios simulated?") - exit(1) - walberla_cpp.log_info_on_root("Simulating Scenario %d of %d :" % (scenario_idx, len(self._scenarios))) - yield get_config_from_scenario(scenario) - else: - for idx, scenario in enumerate(self._scenarios): - if idx < self._startScenario: - continue # Skip over all scenarios with id < startScenario - cfg = None - while cfg is None: - cfg = get_config_from_scenario(scenario) - walberla_cpp.log_info_on_root("Simulating Scenario %d of %d :" % (idx, len(self._scenarios))) - yield cfg + for idx, scenario in enumerate(self._scenarios): + if idx < self._startScenario: + continue # Skip over all scenarios with id < startScenario + cfg = None + while cfg is None: + cfg = get_config_from_scenario(scenario) + # walberla_cpp.log_info_on_root("Simulating Scenario %d of %d :" % (idx + 1, len(self._scenarios))) + yield cfg except ImportError: pass diff --git a/python/waLBerla/core_extension.py b/python/waLBerla/core_extension.py index f06fefa68..be80be130 100644 --- a/python/waLBerla/core_extension.py +++ b/python/waLBerla/core_extension.py @@ -12,7 +12,7 @@ class SliceMaker(object): makeSlice = SliceMaker() -def normalizeSlice(slices, sizes): +def normalize_slice(slices, sizes): """Converts slices with floating point entries to integer slices""" assert (len(slices) == len(sizes)) @@ -29,52 +29,52 @@ def normalizeSlice(slices, sizes): assert (type(s) is slice) if s.start is None: - newStart = 0 + new_start = 0 elif type(s.start) is float: - newStart = int(s.start * size) + new_start = int(s.start * size) else: - newStart = s.start + new_start = s.start if s.stop is None: - newStop = size + new_stop = size elif type(s.stop) is float: - newStop = int(s.stop * size) + new_stop = int(s.stop * size) else: - newStop = s.stop + new_stop = s.stop - result.append(slice(newStart, newStop, s.step)) + result.append(slice(new_start, new_stop, s.step)) return tuple(result) -def sliceToCellInterval(s): - newMin = [0, 0, 0] - newMax = [0, 0, 0] +def slice_to_cell_interval(s): + new_min = [0, 0, 0] + new_max = [0, 0, 0] for i in range(3): if type(s[i]) is int: - newMin[i] = s[i] - newMax[i] = s[i] + new_min[i] = s[i] + new_max[i] = s[i] else: - newMin[i] = s[i].start - newMax[i] = s[i].stop - 1 - return walberla_cpp.CellInterval(newMin, newMax) + new_min[i] = s[i].start + new_max[i] = s[i].stop - 1 + return walberla_cpp.CellInterval(new_min[0], new_min[1], new_min[2], new_max[0], new_max[1], new_max[2]) -def cellIntervalToSlice(cellInterval, collapseExtentOne=True): - if not hasattr(collapseExtentOne, '__len__'): - collapseExtentOne = (collapseExtentOne, collapseExtentOne, collapseExtentOne) +def cell_interval_to_slice(cell_interval, collapse_extent_one=True): + if not hasattr(collapse_extent_one, '__len__'): + collapse_extent_one = (collapse_extent_one, collapse_extent_one, collapse_extent_one) slices = [] - for i, collapseInfo in enumerate(collapseExtentOne): - if collapseInfo and cellInterval.min[i] == cellInterval.max[i]: - slices.append(cellInterval.min[i]) + for i, collapseInfo in enumerate(collapse_extent_one): + if collapseInfo and cell_interval.min[i] == cell_interval.max[i]: + slices.append(cell_interval.min[i]) else: - slices.append(slice(cellInterval.min[i], cellInterval.max[i] + 1, None)) + slices.append(slice(cell_interval.min[i], cell_interval.max[i] + 1, None)) return tuple(slices) -def extend(coreModule): - coreModule.makeSlice = SliceMaker() - coreModule.normalizeSlice = normalizeSlice - coreModule.CellInterval.fromSlice = staticmethod(sliceToCellInterval) - coreModule.CellInterval.toSlice = cellIntervalToSlice +def extend(core_module): + core_module.makeSlice = SliceMaker() + core_module.normalizeSlice = normalize_slice + core_module.CellInterval.fromSlice = staticmethod(slice_to_cell_interval) + core_module.CellInterval.toSlice = cell_interval_to_slice diff --git a/python/waLBerla/cuda_extension.py b/python/waLBerla/cuda_extension.py index 0cbb96163..c7e74a528 100644 --- a/python/waLBerla/cuda_extension.py +++ b/python/waLBerla/cuda_extension.py @@ -1,26 +1,26 @@ from pycuda.gpuarray import GPUArray import numpy as np -from .field_extension import normalizeGhostlayerInfo +from .field_extension import normalize_ghostlayer_info -def toGpuArray(f, withGhostLayers=True): +def to_gpu_array(f, with_ghost_layers=True): """Converts a waLBerla GPUField to a pycuda GPUArray""" if not f: return None dtype = np.dtype(f.dtypeStr) strides = [dtype.itemsize * a for a in f.strides] res = GPUArray(f.sizeWithGhostLayers, dtype, gpudata=f.ptr, strides=strides) - if withGhostLayers is True: + if with_ghost_layers is True: return res - ghostLayers = normalizeGhostlayerInfo(f, withGhostLayers) - glCutoff = [f.nrOfGhostLayers - gl for gl in ghostLayers] - res = res[glCutoff[0]:-glCutoff[0] if glCutoff[0] > 0 else None, - glCutoff[1]:-glCutoff[1] if glCutoff[1] > 0 else None, - glCutoff[2]:-glCutoff[2] if glCutoff[2] > 0 else None, + ghost_layers = normalize_ghostlayer_info(f, with_ghost_layers) + cutoff = [f.nrOfGhostLayers - gl for gl in ghost_layers] + res = res[cutoff[0]:-cutoff[0] if cutoff[0] > 0 else None, + cutoff[1]:-cutoff[1] if cutoff[1] > 0 else None, + cutoff[2]:-cutoff[2] if cutoff[2] > 0 else None, :] return res -def extend(cppCudaModule): - cppCudaModule.toGpuArray = toGpuArray +def extend(cpp_cuda_module): + cpp_cuda_module.toGpuArray = to_gpu_array diff --git a/python/waLBerla/field_extension.py b/python/waLBerla/field_extension.py index 7e08aa730..61a14f751 100644 --- a/python/waLBerla/field_extension.py +++ b/python/waLBerla/field_extension.py @@ -8,11 +8,15 @@ import numpy # ----------------------------- Python functions to extend the C++ field module --------------------------------- -def normalizeGhostlayerInfo(field, withGhostLayers): +def normalize_ghostlayer_info(field, with_ghost_layers): """Takes one ghost layer parameter and returns an integer: - True -> all ghost layers, False->no ghost layers""" + True -> all ghost layers, False->no ghost layers + Args: + field: waLberl field object + with_ghost_layers: see numpy_array_from_walberla_field + """ - def normalizeComponent(gl): + def normalize_component(gl): if gl is False: return 0 if gl is True: @@ -21,17 +25,17 @@ def normalizeGhostlayerInfo(field, withGhostLayers): raise ValueError("Field only has %d ghost layers (requested %d)" % (field.nrOfGhostLayers, gl)) return gl - if hasattr(withGhostLayers, "__len__") and len(withGhostLayers) == 3: - ghostLayers = [normalizeComponent(gl) for gl in withGhostLayers] + if hasattr(with_ghost_layers, "__len__") and len(with_ghost_layers) == 3: + ghost_layers = [normalize_component(gl) for gl in with_ghost_layers] else: - ghostLayers = [normalizeComponent(withGhostLayers)] * 3 - return ghostLayers + ghost_layers = [normalize_component(with_ghost_layers)] * 3 + return ghost_layers -def npArrayFromWaLBerlaField(field, withGhostLayers=False): +def numpy_array_from_walberla_field(field, with_ghost_layers=False): """ Creates a numpy array view on the waLBerla field data @field: the waLBerla field - @withGhostLayers: Possible values: + @with_ghost_layers: Possible values: 1. Boolean: False: no ghost layers included True: all ghost layers included 2. Integer: number of ghost layers to include @@ -41,55 +45,58 @@ def npArrayFromWaLBerlaField(field, withGhostLayers=False): if not field: return None - ghostLayers = normalizeGhostlayerInfo(field, withGhostLayers) - - if not hasattr(field, 'buffer'): # Field adaptor -> create field with adapted values - field = field.copyToField() + if hasattr(field, 'nrOfGhostLayers'): + field_gl = field.nrOfGhostLayers + else: + field_gl = 0 + ghost_layers = normalize_ghostlayer_info(field, with_ghost_layers) - if ghostLayers[0] == 0 and ghostLayers[1] == 0 and ghostLayers[2] == 0: - return numpy.asarray(field.buffer(False)) + if ghost_layers[0] == field_gl and ghost_layers[1] == field_gl and ghost_layers[2] == field_gl: + return numpy.asarray(field) else: - result = numpy.asarray(field.buffer(True)) - glCutoff = [field.nrOfGhostLayers - gl for gl in ghostLayers] - view = result[glCutoff[0]:-glCutoff[0] if glCutoff[0] > 0 else None, - glCutoff[1]:-glCutoff[1] if glCutoff[1] > 0 else None, - glCutoff[2]:-glCutoff[2] if glCutoff[2] > 0 else None, - :] + result = numpy.asarray(field) + cutoff = [abs(gl - field.nrOfGhostLayers) for gl in ghost_layers] + if len(result.shape) == 4: + view = result[cutoff[0]:-cutoff[0] if cutoff[0] > 0 else None, + cutoff[1]:-cutoff[1] if cutoff[1] > 0 else None, + cutoff[2]:-cutoff[2] if cutoff[2] > 0 else None, + :] + else: + view = result[cutoff[0]:-cutoff[0] if cutoff[0] > 0 else None, + cutoff[1]:-cutoff[1] if cutoff[1] > 0 else None, + cutoff[2]:-cutoff[2] if cutoff[2] > 0 else None] return view -def arrayFromWaLBerlaAdaptor(field, withGhostLayers=False): - return npArrayFromWaLBerlaField(field.copyToField(), withGhostLayers) - - -def copyArrayToField(dstField, srcArray, slice=[slice(None, None, None)] * 3, withGhostLayers=False): +def copy_array_to_field(dst_field, src_array, idx=None, with_ghost_layers=False): """ Copies a numpy array into (part of) a waLBerla field Usually no copying has to take place between waLBerla fields and numpy arrays, since an array view can be constructed on a field that uses the same memory. When running certain numpy operations that cannot be done in-place,however, the data has to be copied back. - @param dstField: waLBerla field, where the data is copied to - @param srcArray: numpy array where to copy from - @param slice: the numpy array is allowed to be smaller than the field. In this case the target region + @param dst_field: waLBerla field, where the data is copied to + @param src_array: numpy array where to copy from + @param idx: the numpy array is allowed to be smaller than the field. In this case the target region has to be specified via this 3 dimensional slice - @param withGhostLayers: if true the ghost layers of the field are considered as well + @param with_ghost_layers: if true the ghost layers of the field are considered as well """ - dstAsArray = npArrayFromWaLBerlaField(dstField, withGhostLayers) - numpy.copyto(dstAsArray[slice], srcArray) + if idx is None: + idx = [slice(None, None, None)] * 3 + dst_as_array = numpy_array_from_walberla_field(dst_field, with_ghost_layers) + numpy.copyto(dst_as_array[idx], src_array) -def extend(cppFieldModule): - def gatherField(blocks, blockDataName, sliceObj, allGather=False): - field = cppFieldModule.gather(blocks, blockDataName, sliceObj, targetRank=-1 if allGather else 0) +def extend(cpp_field_module): + def gather_field(blocks, block_data_name, slice_obj, all_gather=False): + field = cpp_field_module.gather(blocks, block_data_name, slice_obj, targetRank=-1 if all_gather else 0) if field is not None: - field = npArrayFromWaLBerlaField(field) + field = numpy_array_from_walberla_field(field) field.flags.writeable = False return field else: return None - cppFieldModule.toArray = npArrayFromWaLBerlaField - cppFieldModule.adaptorToArray = arrayFromWaLBerlaAdaptor - cppFieldModule.copyArrayToField = copyArrayToField - cppFieldModule.gatherField = gatherField + cpp_field_module.toArray = numpy_array_from_walberla_field + cpp_field_module.copyArrayToField = copy_array_to_field + cpp_field_module.gatherField = gather_field diff --git a/python/waLBerla/geometry_setup.py b/python/waLBerla/geometry_setup.py deleted file mode 100644 index 32947a35b..000000000 --- a/python/waLBerla/geometry_setup.py +++ /dev/null @@ -1,148 +0,0 @@ -import numpy as np -import scipy -import scipy.ndimage - -try: - from .walberla_cpp import CellInterval, field -except ImportError: - from walberla_cpp import CellInterval, field - -from .core_extension import normalizeSlice - - -def setBoundaryFromArray(blocks, boundaryID, targetSlice, imageArr, boundaryConfig, - resizeFunc=None, extrusionCoordinate=-1): - """Initializes Boundary Handling using an image - :param blocks: the block storage - :param boundaryID: block data name of boundary handling - :param targetSlice: slice of the domain where the image should be placed. If a 2D slice is given, the image is - automatically extruded along the third coordinate. For 3D (or 1D) slices an extrusionCoordinate - has to be given. - Example: for targetSlice=[0.25:0.75, 0 , 0.25:0.75] the image is resized to half - the x-z domain size, placed in the middle of the domain and extruded in y direction. - :param imageArr: a 2D array used to set up the boundaries - :param boundaryConfig: dictionary mapping values of imageArr to boundary configurations, - used as index array in forceBoundary() - :param resizeFunc: if the given slice does not match the shape of imageArr, the image array has to be resized. - This can not be done automatically since interpolation would change the values, and the mapping - given in boundaryConfig is incorrect. Thus the resize function has to be supplied by the caller. - Here a function "resize(imgArr, newSize)" has to be passed, that returns a resized array of - shape "newSize". - :param extrusionCoordinate: only necessary if 3D or 1D slices are given for targetSlice. - See documentation of targetSlice. - """ - - if len(blocks) == 0: - return - - nrOfGhostLayers = blocks[0][boundaryID].getFlagField().nrOfGhostLayers - - imageArr = np.rot90(imageArr, 3) - - sliceWithGhostLayers = 'g' in targetSlice - targetSlice = [s for s in targetSlice if s != 'g'] - size = [s + 2 * nrOfGhostLayers if sliceWithGhostLayers else s for s in blocks.getDomainCellBB().size] - imageCellInterval = CellInterval.fromSlice(normalizeSlice(targetSlice, size)) - if sliceWithGhostLayers: - imageCellInterval.shift(-nrOfGhostLayers, -nrOfGhostLayers, -nrOfGhostLayers) - - # Automatic detection of extrusion coordinate - if extrusionCoordinate < 0 or extrusionCoordinate > 2: - possibleExtrusionCoordinate = np.array([0, 0, 0]) - for i in range(3): - if imageCellInterval.min[i] == imageCellInterval.max[i]: - possibleExtrusionCoordinate[i] = 1 - extrusionCoordinate = i - if sum(possibleExtrusionCoordinate) != 1: - raise ValueError("No valid extrusionCoordinate given - " - "and extrusion coordinate could not be found automatically") - assert (extrusionCoordinate < 3 and extrusionCoordinate >= 0) - - # Resize image - imageBounds = list(imageCellInterval.size) - del imageBounds[extrusionCoordinate] - if imageArr.shape != tuple(imageBounds): - if resizeFunc is None: - raise ValueError("The given image size does not match the target slice: " - "resizing would be necessary but no resizeFunc was given.") - imageArr = resizeFunc(imageArr, imageBounds) - - assert imageArr.shape == tuple(imageBounds) - assert imageArr.dtype.kind == 'i', "imageArr has to be of integer type" - - unusedIdx = 0 - while unusedIdx in boundaryConfig: - unusedIdx += 1 - - def make2Dfrom3DSlice(targetSlice, extrusionCoordinate): - l = list(targetSlice) - del l[extrusionCoordinate] - return l - - for block in blocks: - blockCellInterval = blocks.getBlockCellBB(block) - blockCellInterval.expand(nrOfGhostLayers) - intersectionGlobalCoord = blockCellInterval.getIntersection(imageCellInterval) - - if intersectionGlobalCoord.empty(): - continue - intersectionLocalCoord = blocks.transformGlobalToLocal(block, intersectionGlobalCoord) - - # Create a field with same size as block - blockCellBB = blocks.getBlockCellBB(block) - wlbIndexField = field.createField(list(blockCellBB.size), np.int32, ghostLayers=nrOfGhostLayers) - indexField = field.toArray(wlbIndexField, withGhostLayers=nrOfGhostLayers)[:, :, :, :] - indexField[:, :, :, :] = unusedIdx - - # Copy image into this field - targetSlice = intersectionLocalCoord.getShifted(nrOfGhostLayers, nrOfGhostLayers, nrOfGhostLayers).toSlice() - - minCoord = np.array(imageCellInterval.min) - imgTargetSlice = intersectionGlobalCoord.getShifted(*(-minCoord)).toSlice() - sliceInImage = make2Dfrom3DSlice(imgTargetSlice, extrusionCoordinate) - indexField[targetSlice + [0]] = imageArr[sliceInImage] - - block[boundaryID].forceBoundary(wlbIndexField, boundaryConfig) - - -def binaryResize(img, newSize): - """This can be used as resize function for setBoundaryFromArray for arrays with - zero and ones. After resizing the image is again binarized""" - img = scipy.misc.imresize(img, size=newSize) - img[img <= 254] = 0 - img[img > 254] = 1 - img = img.astype(np.int32) - return img - - -def setBoundaryFromBlackAndWhiteImage(blocks, boundaryID, targetSlice, imagePath, boundaryConfig, - extrusionCoordinate=-1): - """Loads array from image file and calls setBoundaryFromArray. - - :param imagePath: path to image file. - - For the other parameters see documentation of setBoundaryFromArray. - """ - imgArr = scipy.ndimage.imread(imagePath, flatten=True).astype(int) - setBoundaryFromArray(blocks, boundaryID, targetSlice, imgArr, {0: boundaryConfig}, - resizeFunc=binaryResize, extrusionCoordinate=extrusionCoordinate) - - -def setFieldUsingFlagMask(blocks, targetField, targetValue, flagField, flagNames): - """ - Sets all values of a target field to given value where a certain flag is set. - - :param blocks: the block structure - :param targetField: the field that is modified - :param targetValue: value that is written to all entries of cells where the given flag is set - :param flagField: the flag field - :param flagNames: list of flag names. If one of the flags is set, the target value is written - """ - for b in blocks: - mask = 0 - for flagName in flagNames: - mask |= b[flagField].flag(flagName) - - targetArr = field.toArray(b[targetField], True) - flagArr = field.toArray(b[flagField], True)[:, :, :, 0] - targetArr[np.bitwise_and(flagArr, mask) > 0, :] = targetValue diff --git a/python/waLBerla/plot.py b/python/waLBerla/plot.py index e4b007455..a492823aa 100644 --- a/python/waLBerla/plot.py +++ b/python/waLBerla/plot.py @@ -11,128 +11,147 @@ except ImportError: from matplotlib.pyplot import imshow, gcf, figure, plot, quiver -def fieldShow(npField, **kwargs): - npField = np.rot90(npField, 3) - imshow(npField, origin='lower', **kwargs) +def field_show(numpy_field, **kwargs): + numpy_field = np.rot90(numpy_field, 3) + imshow(numpy_field, origin='lower', **kwargs) -def scalarField(blocks, name, sliceDef, fCoord=0, targetRank=0, **kwargs): +def scalar_field(blocks, name, slice_definition, f_coordinate=0, target_rank=0, **kwargs): """Plots a 2D slice through the global domain as an image - :param blocks: the blockstorage - :param name: Name of the block data to be plotted. Has to be a scalar field - :param sliceDef: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice - :param fCoord: value of the forth field coordinate (f) - :param targetRank: rank that gathers and plots the data - :param kwargs: further keyword arguments are passed to matplotlib.pyplot.imshow + :param blocks: the blockforest + :param name: Name of the block data to be plotted. Has to be a scalar field + :param slice_definition: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice + :param f_coordinate: value of the forth field coordinate (f) + :param target_rank: rank that gathers and plots the data + :param kwargs: further keyword arguments are passed to matplotlib.pyplot.imshow """ - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) + f = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) if f: - npField = np.asarray(f.buffer())[:, :, :, fCoord].squeeze() - npField = np.swapaxes(npField, 0, 1) - imshow(npField, origin='lower', **kwargs) + numpy_field = np.asarray(f).squeeze() + numpy_field = np.swapaxes(numpy_field, 0, 1) + imshow(numpy_field, origin='lower', **kwargs) -def scalarFieldAnimation(blocks, name, sliceDef, runFunction, plotSetupFunction=lambda: None, - plotUpdateFunction=lambda: None, fCoord=0, targetRank=0, interval=30, frames=180, **kwargs): +def scalar_field_animation(blocks, name, slice_definition, run_function, plot_setup_function=lambda: None, + plot_update_function=lambda: None, f_coordinate=0, target_rank=0, + interval=30, frames=180, **kwargs): """Creates animation of 2D slices through the global domain - :param runFunction: function without arguments which is run between frames (should move simulation forward) - :param plotSetupFunction: function without arguments that is called after the plot was initially created. - Can be used to configure plot (set title etc.) - :param plotUpdateFunction: function without arguments that is called when figure is updated - :param interval: passed to matplotlib.animation.FuncAnimation: milliseconds between two frames - :param frames: passed to :class:`matplotlib.animation.FuncAnimation` number of frames + :param blocks: the blockforest + :param name: Name of the block data to be plotted. Has to be a scalar field + :param slice_definition: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice + :param run_function: function without arguments which is run between frames (should move simulation forward) + :param plot_setup_function: function without arguments that is called after the plot was initially created. + Can be used to configure plot (set title etc.) + :param plot_update_function: function without arguments that is called when figure is updated + :param f_coordinate: value of the forth field coordinate (f) + :param target_rank: rank that gathers and plots the data + :param interval: passed to matplotlib.animation.FuncAnimation: milliseconds between two frames + :param frames: passed to :class:`matplotlib.animation.FuncAnimation` number of frames for other params see :func:`scalarField` """ fig = gcf() - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) + f = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) im = None if f: - npField = np.asarray(f.buffer())[:, :, :, fCoord].squeeze() - npField = np.swapaxes(npField, 0, 1) - im = imshow(npField, origin='lower', **kwargs) - plotSetupFunction() + numpy_field = np.asarray(f).squeeze() + numpy_field = np.swapaxes(numpy_field, 0, 1) + im = imshow(numpy_field, origin='lower', **kwargs) + plot_setup_function() def updatefig(*args): - runFunction() - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) - if f: - npField = np.swapaxes(np.asarray(f.buffer()), 0, 1) - npField = npField[:, :, :, fCoord].squeeze() - im.set_array(npField) - plotUpdateFunction() + run_function() + gathered_field = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) + if gathered_field: + n_field = np.swapaxes(np.asarray(gathered_field), 0, 1) + if n_field.shape == 4: + n_field = n_field[:, :, :, f_coordinate].squeeze() + else: + n_field = n_field.squeeze() + im.set_array(n_field) + plot_update_function() return im, return animation.FuncAnimation(fig, updatefig, interval=interval, frames=frames) -def vectorField(blocks, name, sliceDef, xComponent=0, yComponent=1, targetRank=0, xStep=1, yStep=1, **kwargs): +def vector_field(blocks, name, slice_definition, x_component=0, y_component=1, target_rank=0, + x_step=1, y_step=1, **kwargs): """Plots a vector field slice using matplotlib quiver - :param blocks: the blockstorage - :param name: Name of the block data to be plotted. Has to be a scalar field - :param sliceDef: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice - :param xComponent: which component of the vector field (0,1 or 2) - to take as the horizontal value for the quiver arrows - :param yComponent: which component of the vector field (0,1 or 2) - to take as the vertical value for the quiver arrows - :param xStep: take only every xStep's cell/arrow in x direction - :param yStep: take only every yStep's cell/arrow in y direction - :param targetRank: rank that gathers and plots the data - :param kwargs: further keyword arguments are passed to matplotlib.pyplot.quiver + :param blocks: the blockforest + :param name: Name of the block data to be plotted. Has to be a scalar field + :param slice_definition: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice + :param x_component: which component of the vector field (0,1 or 2) + to take as the horizontal value for the quiver arrows + :param y_component: which component of the vector field (0,1 or 2) + to take as the vertical value for the quiver arrows + :param x_step: take only every xStep's cell/arrow in x direction + :param y_step: take only every yStep's cell/arrow in y direction + :param target_rank: rank that gathers and plots the data + :param kwargs: further keyword arguments are passed to matplotlib.pyplot.quiver """ - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) + f = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) if f: - npField = np.swapaxes(np.asarray(f.buffer()), 0, 1) - xVel = npField[::xStep, ::yStep, :, xComponent].squeeze() - yVel = npField[::xStep, ::yStep, :, yComponent].squeeze() - quiver(xVel, yVel, **kwargs) + numpy_field = np.swapaxes(np.asarray(f), 0, 1) + x_vel = numpy_field[::x_step, ::y_step, :, x_component].squeeze() + y_vel = numpy_field[::x_step, ::y_step, :, y_component].squeeze() + quiver(x_vel, y_vel, **kwargs) -def alongLine(blocks, name, sliceDef, fCoord=0, targetRank=0, **kwargs): +def along_line(blocks, name, slice_definition, f_coordinate=0, target_rank=0, **kwargs): """Plot a field value along a one dimensional slice through the domain - :param blocks: the blockstorage - :param name: Name of the block data to be plotted. Has to be a scalar field - :param sliceDef: a one dimensional slice through the domain. Can be created with :func:`waLBerla.makeSlice` - :param fCoord: value of the forth field coordinate (f) - :param targetRank: rank that gathers and plots the data - :param kwargs: further keyword arguments are passed to :func:`matplotlib.pyplot.plot` + :param blocks: the blockstorage + :param name: Name of the block data to be plotted. Has to be a scalar field + :param slice_definition: a one dimensional slice through the domain. Can be created with :func:`waLBerla.makeSlice` + :param f_coordinate: value of the forth field coordinate (f) + :param target_rank: rank that gathers and plots the data + :param kwargs: further keyword arguments are passed to :func:`matplotlib.pyplot.plot` """ - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) + f = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) if f: - npField = np.asarray(f.buffer()) - npField = npField[:, :, :, fCoord].squeeze() + npField = np.asarray(f) + npField = npField[:, :, :, f_coordinate].squeeze() plot(npField, **kwargs) -def alongLineAnimation(blocks, name, sliceDef, runFunction, plotSetupFunction=lambda: None, fCoord=0, targetRank=0, - interval=30, frames=180, **kwargs): +def along_line_animation(blocks, name, slice_definition, run_function, plot_setup_function=lambda: None, + f_coordinate=0, target_rank=0, interval=30, frames=180, **kwargs): """Animated version of :func:`alongLine` - For parameter documentation see :func:`scalarFieldAnimation` and :func:`alongLine` + :param blocks: the blockforest + :param name: Name of the block data to be plotted. Has to be a scalar field + :param slice_definition: a two dimensional slice through the domain. Can be created with waLBerla.makeSlice + :param run_function: function without arguments which is run between frames (should move simulation forward) + :param plot_setup_function: function without arguments that is called after the plot was initially created. + Can be used to configure plot (set title etc.) + :param f_coordinate: value of the forth field coordinate (f) + :param target_rank: rank that gathers and plots the data + :param interval: passed to matplotlib.animation.FuncAnimation: milliseconds between two frames + :param frames: passed to :class:`matplotlib.animation.FuncAnimation` number of frames """ fig = figure() - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) + f = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) line = None if f: - npField = np.asarray(f.buffer()) - npField = npField[:, :, :, fCoord].squeeze() - line, = plot(npField, **kwargs) - plotSetupFunction() + numpy_field = np.asarray(f) + numpy_field = numpy_field[:, :, :, f_coordinate].squeeze() + line, = plot(numpy_field, **kwargs) + plot_setup_function() def updatefig(*args): - runFunction() - f = walberla_cpp.field.gather(blocks, name, sliceDef, targetRank=targetRank) - if f: - npField = np.asarray(f.buffer()) - npField = npField[:, :, :, fCoord].squeeze() - fig.gca().set_ylim((np.min(npField), np.max(npField))) - line.set_ydata(npField) + run_function() + gathered_array = walberla_cpp.field.gather(blocks, name, slice_definition, targetRank=target_rank) + if gathered_array: + n_field = np.asarray(gathered_array) + n_field = n_field[:, :, :, f_coordinate].squeeze() + fig.gca().set_ylim((np.min(n_field), np.max(n_field))) + line.set_ydata(n_field) return line, return animation.FuncAnimation(fig, updatefig, interval=interval, frames=frames, blit=False) diff --git a/python/waLBerla/timeloop_extension.py b/python/waLBerla/timeloop_extension.py deleted file mode 100644 index 31b859ad1..000000000 --- a/python/waLBerla/timeloop_extension.py +++ /dev/null @@ -1,69 +0,0 @@ -try: - from . import walberla_cpp -except ImportError: - import walberla_cpp - - -def functorFromSweep(blocks, sweep): - """Makes a functor from a sweep, by iterating over all the blocks in the provided block storage""" - - def functor(): - for b in blocks: - sweep(b) - - return functor - - -class Timeloop(walberla_cpp.timeloop.ITimeloop): - def __init__(self, nrOfTimesteps): - super().__init__() - self._nrOfTimesteps = nrOfTimesteps - self._timestep = 0 - self._functors = [] - self._stopFlag = False - - def run(self, timesteps=None): - if not timesteps: - timesteps = self._nrOfTimesteps - for t in range(timesteps): - self.singleStep() - if self._stopFlag: - break - - def singleStep(self): - for func in self._functors: - func() - self._timestep += 1 - - def stop(self): - self._stopFlag = True - - def synchronizedStop(self, stop=True): - # syncStop = wlb.mpi.allreduceInt(int(stop), wlb.mpi.LOGICAL_OR) - self._stopFlag = True - - def setCurrentTimeStep(self, ts): - self._timestep = ts - - def getCurrentTimeStep(self): - return self._timestep - - def getNrOfTimeSteps(self): - return self._nrOfTimesteps - - def __getFunctor(self, functor, blocks): - if blocks: # assume that it is a sweep if blocks were given - return functorFromSweep(blocks, functor) - else: - return functor - - def add(self, functor, blocks=None): - self._functors.append(self.__getFunctor(functor, blocks)) - return len(self._functors) - 1 - - def replace(self, handle, functor, blocks=None): - self._functors[handle] = self.__getFunctor(functor, blocks) - - -def extend(cppTimeloopModule): - cppTimeloopModule.Timeloop = Timeloop diff --git a/python/waLBerla/tools/sqlitedb/insert.py b/python/waLBerla/tools/sqlitedb/insert.py index 29fa37c16..2e759af07 100644 --- a/python/waLBerla/tools/sqlitedb/insert.py +++ b/python/waLBerla/tools/sqlitedb/insert.py @@ -13,16 +13,16 @@ def sequenceValuesToScalars(data): This is useful when using the dictionary with storeSingle(), since each entry gets a separate column after the sequences have been separated. """ - keysToDelete = [] - newValues = {} + keys_to_delete = [] + new_values = {} for key, value in data.items(): if type(value) in [list, tuple]: - keysToDelete.append(key) + keys_to_delete.append(key) for i in range(len(value)): - newValues["%s_%d" % (key, i)] = value[i] - for k in keysToDelete: + new_values["%s_%d" % (key, i)] = value[i] + for k in keys_to_delete: del data[k] - data.update(newValues) + data.update(new_values) def storeSingle(data, tableName, dbFile="database.sqlite", runId=None): @@ -40,9 +40,9 @@ def storeSingle(data, tableName, dbFile="database.sqlite", runId=None): if runId: data['runId'] = runId - keyString = ",".join(data.keys()) - valueString = ",".join(["?" for e in data.values()]) - query = "INSERT INTO %s ( %s ) VALUES ( %s )" % (tableName, keyString, valueString) + key_string = ",".join(data.keys()) + value_string = ",".join(["?" for e in data.values()]) + query = "INSERT INTO %s ( %s ) VALUES ( %s )" % (tableName, key_string, value_string) conn = sqlite3.connect(dbFile) c = conn.cursor() @@ -76,9 +76,9 @@ def storeMultiple(data, tableName, dbFile="database.sqlite", runId=None): if runId is not None: data.update({'runId': [runId] * list_length}) - keyString = ",".join(data.keys()) - valueString = ",".join(["?" for e in data.values()]) - query = "INSERT INTO %s ( %s ) VALUES ( %s )" % (tableName, keyString, valueString) + key_string = ",".join(data.keys()) + value_string = ",".join(["?" for e in data.values()]) + query = "INSERT INTO %s ( %s ) VALUES ( %s )" % (tableName, key_string, value_string) conn = sqlite3.connect(dbFile) c = conn.cursor() @@ -93,32 +93,37 @@ def storeMultiple(data, tableName, dbFile="database.sqlite", runId=None): return lastrowid -def checkAndUpdateSchema(data, tableName, dbFile="database.sqlite", referenceRuns=False): +def checkAndUpdateSchema(data, tableName, dbFile="database.sqlite", referenceRuns=False, alter_table=False): """Alters a sqlite table in order to match the given data: * if table with given name does not exist yet, it is created * keys in the data dictionary correspond to columns - * columns are added if necessary, existing data has NULL in these new columns + * columns are added if necessary (only when alter_table is set to True), + * existing data has NULL in these new columns :param data: see :func:`storeSingle` or :func:`storeMultiple` + :param tableName name of the table which should be updated + :param dbFile name of the sql file which should be written :param referenceRuns: if False the table gets an autoincrementing integer column 'runId' if True, a normal column runId is created that points into another table with runId as primary key + :param alter_table If True the columns of the table will be altered. + Should be called if new columns should be inserted. """ def pythonToSqlType(python_type): if python_type is int: - return ("INTEGER") + return "INTEGER" elif python_type is bool: - return ("INTEGER") + return "INTEGER" elif python_type is float: - return ("DOUBLE") + return "DOUBLE" elif python_type is str: - return ("TEXT") + return "TEXT" elif python_type is list: - return (pythonToSqlType(type(value[0]))) + return pythonToSqlType(type(value[0])) elif python_type is tuple: - return (pythonToSqlType(type(value[0]))) + return pythonToSqlType(type(value[0])) if referenceRuns: names = ["runId"] @@ -131,21 +136,22 @@ def checkAndUpdateSchema(data, tableName, dbFile="database.sqlite", referenceRun names.append(key) types.append(pythonToSqlType(type(value))) - columns = [("%s %s") % e for e in zip(names, types)] + columns = ["%s %s" % e for e in zip(names, types)] - createQuery = "CREATE TABLE IF NOT EXISTS %s ( %s );" % (tableName, ",".join(columns)) - alterQueries = ["ALTER TABLE %s ADD COLUMN %s %s;" % (tableName, key, typ) for key, typ in zip(names, types)] + create_query = "CREATE TABLE IF NOT EXISTS %s ( %s );" % (tableName, ",".join(columns)) + alter_queries = ["ALTER TABLE %s ADD COLUMN %s %s;" % (tableName, key, typ) for key, typ in zip(names, types)] conn = sqlite3.connect(dbFile) c = conn.cursor() - c.execute(createQuery) - for q in alterQueries: - try: - c.execute(q) - except sqlite3.OperationalError as e: - print(e) - pass + c.execute(create_query) + if alter_table: + for q in alter_queries: + try: + c.execute(q) + except sqlite3.OperationalError as e: + print(e) + pass conn.commit() conn.close() diff --git a/python/waLBerla/tools/sqlitedb/merge.py b/python/waLBerla/tools/sqlitedb/merge.py index 5628b1f39..b853e3272 100755 --- a/python/waLBerla/tools/sqlitedb/merge.py +++ b/python/waLBerla/tools/sqlitedb/merge.py @@ -28,12 +28,12 @@ def mergeSqliteFiles(targetFile, fileToMerge): db = sqlite3.connect(targetFile) db.execute('ATTACH "' + fileToMerge + '" AS toMerge') - targetColumns = getColumnNames(db, "runs", "main") - toMergeColumns = getColumnNames(db, "runs", "toMerge") + target_columns = getColumnNames(db, "runs", "main") + to_merge_columns = getColumnNames(db, "runs", "toMerge") - columnsToCreate = [e for e in toMergeColumns if e not in targetColumns] + columns_to_create = [e for e in to_merge_columns if e not in target_columns] - for column in columnsToCreate: + for column in columns_to_create: print("Adding Column {} to run table of {} ".format(column[0], targetFile)) db.execute("ALTER TABLE main.runs ADD COLUMN %s %s" % (column[0], column[1])) @@ -41,12 +41,12 @@ def mergeSqliteFiles(targetFile, fileToMerge): # check if an entry with same date exists, if not add the run and the timing pool entries # to the targetTable c = db.cursor() - assert (toMergeColumns[0][0] == "runId") - columns = [e[0] for e in toMergeColumns] - columnString = ",".join(columns) - columnStringNoRunId = ",".join(columns[1:]) + assert (to_merge_columns[0][0] == "runId") + columns = [e[0] for e in to_merge_columns] + column_string = ",".join(columns) + column_string_no_run_id = ",".join(columns[1:]) - query = 'SELECT {} FROM toMerge.runs WHERE timestamp || " " || uuid NOT IN '.format(columnString, ) + query = 'SELECT {} FROM toMerge.runs WHERE timestamp || " " || uuid NOT IN '.format(column_string, ) query += '( SELECT timestamp || " " || uuid FROM main.runs )' # associated tables are tables that reference the runs table, having a first column of 'runId' which is a @@ -80,7 +80,7 @@ def mergeSqliteFiles(targetFile, fileToMerge): # Build up insert statement for 'runs' table questionMarkList = ['?'] * (len(run) - 1) questionMarkString = ",".join(questionMarkList) - insertStatement = "INSERT INTO main.runs (%s) VALUES (%s);" % (columnStringNoRunId, questionMarkString) + insertStatement = "INSERT INTO main.runs (%s) VALUES (%s);" % (column_string_no_run_id, questionMarkString) # Execute the insert insertCursor = db.cursor() insertCursor.execute(insertStatement, run[1:]) diff --git a/python/waLBerla_docs/conf.py b/python/waLBerla_docs/conf.py index e872f555a..b1e67ed6c 100644 --- a/python/waLBerla_docs/conf.py +++ b/python/waLBerla_docs/conf.py @@ -25,7 +25,7 @@ source_suffix = '.rst' master_doc = 'index' project = 'waLBerla' -copyright = '2016, LSS waLBerla Team' +copyright = '2020, LSS waLBerla Team' version = '' release = '' @@ -36,7 +36,7 @@ pygments_style = 'sphinx' htmlhelp_basename = 'waLBerladoc' -intersphinx_mapping = {'python': ('http://docs.python.org/3.5', None), +intersphinx_mapping = {'python': ('http://docs.python.org/3.8', None), 'numpy': ('http://docs.scipy.org/doc/numpy/', None), 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), 'matplotlib': ('http://matplotlib.sourceforge.net/', None)} diff --git a/python/waLBerla_docs/index.rst b/python/waLBerla_docs/index.rst index 78964d95e..aa70a63de 100644 --- a/python/waLBerla_docs/index.rst +++ b/python/waLBerla_docs/index.rst @@ -4,34 +4,23 @@ Documentation of waLBerla's Python interface Quick Start / Tutorials: ------------------------ -You can quickly try out waLBerla's Python interface in a hosted IPython notebook without -having to install anything: - -http://demo.walberla.net - -This site contains interactive tutorials, illustrating how to set up a 2D lattice Boltzmann -simulation with waLBerla. - - -Installation with conda: +This is the documentation of waLBerla`s Python coupling. +It enables to use the core functionality of waLBerla from Python. A primary advantage of +the Python coupling is that it allows having a view on waLBerla`s data as +NumPy arrays. With this, it is easily possible to provide pre and postprocessing +routines using powerful packages from Python. Furthermore, it is possible to set up entire +simulations just from Python. This feature is primarily used in the code generation framework `pystencils <https://pycodegen.pages.i10git.cs.fau.de/pystencils/>`_ +within its `parallel data handling <https://pycodegen.pages.i10git.cs.fau.de/pystencils/notebooks/03_tutorial_datahandling.html>`_ which entirely builds upon waLBerla`s Python coupling. +waLBerla`s Python bindings are built with `pybind11 <https://pybind11.readthedocs.io/en/stable/#>`_, a lightweight header-only package which is shipped as a submodule. +Thus, there is no need to install any additional software. + + +Installation: ------------------------ -To run waLBerla on your own machine the simplest way to get going is the installation via -the `conda package manager <http://conda.pydata.org>`_:: - - conda install --channel lssfau walberla - - -Run in docker: --------------- - -Docker is a lightweight virtualization solution. We provide a docker image that -contains the same environment as hosted on http://demo.walberla.net. -With this image you can run and develop waLBerla simulations on your own machine without having to manually -install the dependencies. All you need is a running installation of `Docker <www.docker.com>`_. -Run the waLBerla image with the following command and navigate in your browser to http://localhost:8888 :: +To install waLberla as Python package in your path run the following command in your build folder :: - docker run -it -p 8888:8888 walberla/runenv-ubuntu-python + make pythonModuleInstall API Documentation: @@ -44,8 +33,6 @@ API Documentation: modules/blockforest modules/core modules/field - modules/geometry - modules/lbm modules/plot modules/tools diff --git a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 01 - Basic data structures.ipynb b/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 01 - Basic data structures.ipynb index df203fccd..56093faa5 100644 --- a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 01 - Basic data structures.ipynb +++ b/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 01 - Basic data structures.ipynb @@ -24,11 +24,21 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0 0 0 0 0]\n", + " [0 0 0 0 0]\n", + " [0 0 0 0 0]\n", + " [0 0 0 0 0]\n", + " [0 0 0 0 0]]\n" + ] + } + ], "source": [ "import numpy as np\n", "def makeGrid(shape):\n", @@ -46,10 +56,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "ALIVE = 1\n", @@ -86,11 +94,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]\n" + ] + } + ], "source": [ "print(neighborhoodD2Q9)" ] @@ -104,11 +118,34 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial Setup:\n", + "[[0 0 0 0 0]\n", + " [0 0 0 0 0]\n", + " [0 1 1 1 0]\n", + " [0 0 0 0 0]\n", + " [0 0 0 0 0]]\n", + "After timestep 1: \n", + "[[0 0 0 0 0]\n", + " [0 0 1 0 0]\n", + " [0 0 1 0 0]\n", + " [0 0 1 0 0]\n", + " [0 0 0 0 0]]\n", + "After timestep 2: \n", + "[[0 0 0 0 0]\n", + " [0 0 0 0 0]\n", + " [0 1 1 1 0]\n", + " [0 0 0 0 0]\n", + " [0 0 0 0 0]]\n" + ] + } + ], "source": [ "grid = makeGrid( [5,5] )\n", "grid[2,1:4] = ALIVE\n", @@ -132,12 +169,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { - "collapsed": false, "scrolled": false }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "<Figure size 1080x864 with 1 Axes>" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "from material.matplotlib_setup import * # import matplotlib and configures it to play nicely with iPython notebook\n", "matplotlib.rcParams['image.cmap'] = 'Blues' # switch default colormap\n", @@ -154,11 +203,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<video controls width=\"80%\">\n", + " <source src=\"data:video/x-m4v;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAGlJtZGF0AAACcQYF//9t3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MCByMzAxMSBjZGU5YTkzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTAgcmVmPTIgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTI0IGxvb2thaGVhZF90aHJlYWRzPTQgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbnRfbWluPTIgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIzLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAA/hZYiEBX///w9FAAE/zycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddf/4R8FoIgAMjdgACAUAAIAtTYAUERgAAgDgACACANHpYHzIq36zo0yT9Kmtra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tr/+H2CASABHf2wABANAAEAu5sAEkwy7TUENddddddddddddddddddddddddczBR2UgU0tddddddddddddddddddddddddddddddddddddddPrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666666666666/90hDUrBFwAIgviKY+if5AACAe8LwEkBxOwxHaS/8uv9s2VGGbFf+3/9RnkgFtzg0X1w09Y+FxdV/3ChZMDuhyQknln+BYf/9gk+QAD1BUAVblVKmGamQwbknqCOuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrvp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuv/EJwGGwRcABMTFOVhncstbnL/HBHoZITDETHEyPA8db72gCIf9UU9/ctKucufgACBYAAkzIHwJ/gDTACLTbCggt/jpk+GOcABBlIQhSkIc5CEOf/AAoD+BMEXbmpgyJThh/UO11111111111111111111111111111111PXXXXXXXT1111111111111111111111111309ddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXT111111111111111111111111111111111111111109ddddddddddddddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111111111111111109ddddddf//jgtBhwHAAIAYKBFMCj7Acx4GW/HAJnHOPJlra2tra2tra2tra2tra2tra2tra2tra//j4IFxeA4ABUA4LJpgCIB+CEFFVtMDO/Jgxrrrrrrrp666666666666666666666666666euuuuuuuKYNbP3QzQmQK6OA0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sUwtEJy8KizVMP111111109ddddddddddddddddddddddddddPXXXXXXXMwZXqCuuuuuuuuuuuuuuuuuuuuuuunpnrrrrrrrp666666666666666666666666666euuuuuuunrrrrrrrrrrrrrrrrrrrrrrrp6euuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrr//iKM0KgggBmVM0T1e+AIwIrx64v1lFTikZUEr9943AAGgMOIQbN+djTxSn3/F+3s2Z8P/+wQf+MAAtAdYH2/Nahxq5sNQ3XXXXXXT1111111111111111111111109M9dddddddPXXXXXXXXXXXXXXXXXXXXXXXXX+QQ/4SBBAATJxDRflydFh+LRDKAAICRM5GgIOwCfiFqhGbr1Tu4YY5wcGVl9lZfUENddddddPXXXXXXXXXXXXXXXXXXXXXXXT09dddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXT111111109dddddddddddddddddddddddPT111111109ddddddddddddddddddddddddddPXXXXXXXT1111111111111111111111109PXXXXXXXT1111111111111111111111111109ddddddfD/pgDDoMOAAkjEXHWmBsKW1uCByoy4Vy+XRaLXqa2Ewmtra2tra2tra2tra2tra2tra2tra2v8PH7BcTgAN0ZGxPmOjsxUZYVyzH4ZdLqYMa6666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrri0adEV+8m7W1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tanrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuv//4QQggwAHww6QDN3IroPh29kG//nCEZSkOVB1J6ghrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666666666666/ieH/CQIIAKfEjHUZXP+BAe3YIWTX4n8qAhKv+C5pBtWX6tPLDD//+w99gXUG0Xdya2zovOtDGWaghrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666666666666///4IQ5gANAB7gBIERE4lEClpCpDT1DtdddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXX2pAf/CQIoAFkXoR/K8RF76BHYxniI3skynCu8EYaCMrvAsW7g7xiD5wBKA9v4WcqalQn+DHOAAgylKUpSlKUpSlKABQH8CYIu3VTBkSnDD//qCOuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrp66666666666666666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrwoGAf6BWCLAAQjsgrFkJrWNT2mERiSxEdU+3tbW18f7CMKfgAIG4U1uI4kBghVZh+xweAA2TNoTZXVmXxYQhDOHxwgwz59noQR/TRgKwJmQ//eNAYOi58wbDc5hAbA7g9ra2tra2tr48AjT0J8ABGJziL4kgcI61ZPGOYAAIA4AAgBgDxmW/oG0RwYIbtREXYIN/EkFVxwMFo1cmt+a2tra2tra+P7UgNDOAAgThDG73kgNEK7MPmILAIHq77v/zwNwnHTLSHAeeiKgvgQc8Ula9Stis90DST1TSSMw3ynf/w/QIDLDtbW1tbW18f+AKKCheAAkJiGIwzhZlLnBVSAAmJinOwR3LNaQQv2NfGFGp1h7DruA+AEKuQW2QiZMMKa8jf8P6I1tbW1tbXx/8YoKeAAgnGKXve8DTGdeaM9AxDd+jBFUU8KU36CwBVgCn6ogELH3/rn/UA+gnwX5EMj6cjvrPQxtm1tbXC3/9h4uAAhnK471lrv3vX/hqCGuuuuuuuuuuuuuuuuuuuuuuuuuuuuvRh//w8CDwyFYT6W1RJHPOL8AEoZawSXci3vghPNWGGoIa66666+EMP/CQIPkACUxwYD3SdzDQtHnXD9g5DAChLlI7SaQdbDDqCGuuuuuvtth/wkCCAC0NYQxT3E5//9/8DYgYcoGbJJLWpNF7//vGPRCu26omdQw1BCGuuuuuv8v/w+HgAHCFTIhtNKW6OKzQ3AIwc8CTr0G2BptbVXBhqHw111111/z/+Hw9AWwTEABVareALCMqVDv//4e/IAJgMS/PaGu4gL6hh1D9ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeAAAC9EGaOAr4ArbigScAK5/dWIBcerfSqH4mA4AEX1JmPAAi+pM/LSfbT5aDMAAi+xMwCQG//iGC4YA4ABADBQIy3wgJSkCBZF4XZfJsILydf0EgohcQuIXELiFxC5v8eMMF0AHcxMIwhhAe7vn/8BEA+F2WHkVzJoNXcB4LelxaBXHAbOudc651zrnXHsFX/mvweHfOwV5/P5/P5/P53g8O+d8/n8/n8/n87wWcSHgHAAi+xMwcACL7EzAWCmZV6IOrx4AEX2JmEDvnYfz+fz+fz+fzvB4d875/P5/P5/P53g8O+d8/n8/n8/n87weHfO+fz+fz+fz+d4ND+fz+fz+fz+fzvnfP5/P5/P5/O+d8/n8/n8/n8/BYfz+fz+fz+fz+d4g753z+fz+fz+fz8Fh/P5/P5/P5/P53iDvnfP5/P5/P5/PwWH8/n8/n8/n8/neIO+d8/n8/n8/n8/BJxIeAcACL7EzBwAIvsTPgAI01J2nu4MQeH8/n8/n8/n8/neIO+d8/n8/n8/n8/BIdh4sUEHh/P5/P5/P5/P53iDvnfP5/P5/P5/PwWH8/n8/n8/n8/neIO+d8/n8/n8/n8/BYfz+fz+fz+fz+d4g753z+fz+fz+fz8Fh/P5/P5/P5/P53zvn8/n8/n8/nfO+fz+fz+fz+fgsPLn8/n8/n8/n8753z+fz+fz+fzvnfP5/P5/P5/PwaHfO+fz+fz+fz+d4PDvnfP5/P5/P5/O8FfMCDgIX6QdU7wgd87Dufz+fz+fz+d4PDvnfP5/P5/P5/O8Hh3zvn8/n8/n8/neDw753z+fz+fz+fzvB4d8exEyKsul3NptnXOudc651zrneDw3x/9AsDgDgAFwBwJLS3wFgEwZKX4XZfjFpFELiFxC4hcQuIXMPf/6BZAASiFFcuSCrelYYcAlhV/6eA+AmzgAIbu7u7u7u7u4AFA/glGefZHBJy4Yf/8AinLAn/Jr/hDm8Cd6S0I4rwAeSTrHW5n12Qj/6qdfANlAAAAURBmkAU8AcncCJffffffffcHl99999999weX33333333B5ffffffffcHl99999999weX33333333B5ffffffffcHl99999999waX333333333333333333333333BZgBBjPfcRffffffffcFnz33EX33333333BZ899xF99999999wWfPfcRffffffffcFnz33EX33333333BZ899xF99999999wWfPfcRffffffffcFiv777777777777777777777777gtvvvvvvvvvvvvvvvvvvvvvvvuDS++++++++4PL77777777gr8wIOCF4c+J3hC++++++++4PL77777777g8vvvvvvvvuDy++++++++4PL77777777g8vvvvvvvvvuA9/EwAHRvWSlz///hmAAEh0uTMHAAJDpcmYOAASHS5MwBb0AAAE2QZpgFPAHr333333333B5ffffffffaBc8Hl99999999weX33333333B5ffffffffcHl99999999weX33333333B5ffffffffcGl999999999999999999999999wWX33333333EX33333333BZffffffffcRffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffcRffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffffffffffffffffffcFt99999999999999999999999waX33333333B5ffffffffcFh2JhC++++++++4PL77777777g8vvvvvvvvuDy++++++++4PL77777777g8vvvvvvvvuAPWgAAAATxBmoAU8AevffffffffcHl99999999weX333333333B3fffffffffcHd999999999wd3333333333B3fffffffffcHd999999999wZ3333333333333333333333333BZffffffffcRffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffcRffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffcRffffffffcFl999999999999999999999999wW33333333333333333333333BrfffffffffcHd999999999wVneEL777777777g7vvvvvvvvvuDu+++++++++4O7777777777g7vvvvvvvvvuDu++++++++4A9aAAAABMkGaoBLwB6999999999weX33333333B5ffffffffcHl99999999weX33333333B5ffffffffcHl99999999weX33333333BpffffffffffffffffffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffcRffffffffcFl99999999xF99999999wWX33333333EX33333333BZffffffffcRffffffffcFl99999999xF99999999wWX333333333333333333333333BbffffffffffffffffffffffcGt99999999weX33333333B5ffffffffcHl99999999weX33333333B5ffffffffcHl99999999oFTweX33333333AHrQAAAAxltb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAALuAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACQ3RyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAALuAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAEOAAAA2AAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAC7gAAAAAAAEAAAAAAbttZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAADAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFmbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABJnN0YmwAAACWc3RzZAAAAAAAAAABAAAAhmF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAEOANgAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAwYXZjQwFCwB7/4QAYZ0LAHtsBEBt5eEAAAAMAQAAAAwEDxYu4AQAFaMqDyyAAAAAYc3R0cwAAAAAAAAABAAAABgAAIAAAAAAUc3RzcwAAAAAAAAABAAAAAQAAABxzdHNjAAAAAAAAAAEAAAABAAAABgAAAAEAAAAsc3RzegAAAAAAAAAAAAAABgAAEloAAAL4AAABSAAAAToAAAFAAAABNgAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1OC40NS4xMDA=\" type=\"video/mp4\">\n", + " Your browser does not support the video tag.\n", + "</video>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "ani = makeImshowAnimation(grid, gameOfLifeSweep, frames=6)\n", "displayAsHtmlVideo(ani, fps=2)" @@ -173,14 +237,29 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<video controls width=\"80%\">\n", + " <source src=\"data:video/x-m4v;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQABLEttZGF0AAACcgYF//9u3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MCByMzAxMSBjZGU5YTkzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTAgcmVmPTIgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTI0IGxvb2thaGVhZF90aHJlYWRzPTQgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbnRfbWluPTE1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAY3GWIhA7yYoAAvBScnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddf/+E1hQMYACRjUmqjMEtyWKP6AZhHinGE/EyloXvzhBqaz1e/3yYYTojX7HTV3/GCpKBaq+eHzdW7hjD/lwAEYTuItBLDg9HnBBP4ZgAAgOAAQa1LfDAASAD1C8HUxFCbydXwwzS2gg7W1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1x84f6BYEYACICQ0GQgw8PpyrfYNzvXumGQ111111111111131zPHMcaWlpaWlpaWlpaWlpaWlpaWlpaWlpaX+If9AsBQA4BDHALlvgwACMCAS595N/mH/wWQAGUpREM/Z/AxjwwpIa+fSQkgTXuZghj47SPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXT11111111111111zQTXpgnrrrrrrrrrrrrrrrrrrrrr+A//hoFEACKKcZCF+sc2Cg1xABGiBLLA+lhqTV/iH/oFkBwEKaCpb4OAEUEBbBlS/Eu8AzD/0CyAAmZjrocqRoQ1DDgSAIggX4F1+K+Enr8//w0UeaPgAUUpQkc6kL//+GNqYYUkQW18tkMYuE188EwQ1LXXXXXXXXXXXXXXXXXXXXXXXXT11111111111111109ddddddddddfEP/8FYIgOAAgAwEsviCAAIAyEgAaBqcsqnL8GVLKVL8WhYrb+prnD/+wVmAArijujOVExVwYwpknhmuZSrmeE8Kh+uuuuv4D/+CsF0ARTYMxj1q+DgiQYjlgF2WFm5qTTQ8AAA/+GiwBWEqDkPA+2HdV+A/Dgiw0aX6FMs0Nmn1+AYh/6BZA4CAj2XwrDIB5HgdY5fg9bwDb/6BZACb0iJEtXv3ZgLOsKZfn3/H/2CwFIqPiAHxh46JiDomIOM+DjPXXUt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111111111111///OFQxwAELEMiuLANGu8fYDX9DoB3q0wkYaNZ7QL8MdAL8ElhWKT2RR/LphmuuuuuuuuulpfhgAf4aBA7gOAATAYPIS274YE1hgvIMVLKKl+Oy1svzqQkVWH8P7BXwAG6MS8hcx2KRgwBGcGrueSFQQ11111111111111111111111111111111111109ddddddddddddfCDBD/hIMeBoISk6VOTAaeiV+h+wCUBg2OOaUCUt+lWPQMA3zIYVM4lHcP4MP//7BFDAAaL8JZemSV48vV+DGaWoZrrrrrrrrrrrpaWlrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666euuuv//iAwXAuAAiq5og12ESGDuFbQCPD491//rDBdAAhTReYWM6qGWaa79EYd0wX11111111111111111111111111111111111111111111109ddddddddddddddPXXXXMwYUsz5PTBXU9ddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXT111109PT11111111111111111111111111111111111111111111109ddddddddddddddPXXXX+H+AWC4GAAGOrbkm1/w3YcBEA/DX3b/4Bv4LoAGRbOYVMnX+DDimZRGB3VHXbpgxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666/gkBh/CQYgAIWIZFcWAaNd4+wGvgaCEpOlTkwGnolfmABAGgHFygE91AYcA/wH1/i7vrX7FDFRdYtRPITWAMMbu6QIpCove/9h//sPf+AA0X4Sy9Mkjx5e76hmuuulta6666666666666666666666666666666666666666666666euuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrp66666666666666666666666666666666666666666666666666666euuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666/9FlFi7DXAAaoiCHIZ3KHneGfgInAMT3QPN3tagI//3sOgG0nTViSmyacZfCYCJe8hwu3Ks+9eHphA71UPQK06oNT3+5CgBWowtwkoYvyv//8PD//YIIYADRXjWTpUs8WTq3DUN11111111111111111111111111111111111111111111111111109ddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111109ddddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXT111111111111111111111111111111111111111111111111111109ddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111/9YyFxew1wAGtGJwRi64kJNcI/ouh3GUy1vEvaj77EhHeMYKYl/iF3eEGQs9qGEvlgY7ygXKkVPvFchTRgGpGDpFkZsL17vtRrjAUkTLVgQj+/veMDaNgEvv+XHUEe2Bh7NNgpZ2IR/jnAAQmd3d2dmZmd8P/gAUHcNRGl27hZKoYYfUN11111111111111111111111111111111111111111111111111109ddddddddddddd9PXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111109ddddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXT111111111111111111111111111111111111111111111111111109ddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111//xqOFQxAATGxTFBnN5RYae8V8G2gDBLMJzRY807WBBbVhhOliKUvgD44RBZqCCTGv1YIIlHFgCaVumX/usQEDXsAWNVw8TG59h/29hPYsP4ABDVAZ0iC7+vuP/OUM11111111111111111111111111111111111111111111111111109ddddddddddddfLt/8JBbxAF1UIBtLQIlo/Kf0PyGoKGYnVZPu1hGGDzAZwexaq0E9UYuhEMPw//YehjwABACAAUAwwmdQvXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111109ddddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXT111111111111111111111111111111111111111111111111111109ddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT1111111111111//+KwqFuAAjGxTHAxD+UaCRTBT+QSsP5LCteSId4x7cxMItccwsVtVPUL11111111111111111111111111111111111111111111111111109ddddddddddddfGwvf/hIMeDbQBglmE5oseadviBP3CA/M6WyaUkT9HFQCeJk6VlxHf9Q1wgtNuXLAtGi/veZgJhhS+YX+TIZRn7YDD6eJgptq2uXzhH4QgCx/wAGD+CUVptkcPLVfDMAAPAAUBhRMgI+/lnb48AA4ADgKPywEj9e2CWCgpwLusJdYPMvT//gAwDQIJBMvYAAAhNbW1tbW1tbW1tY9QAEGUhyEMU5jGOY5/+AgfodAhtEmNwMC8UuKfDtbW1tbW1tbW1/8f6Ct/ASf5eLcAdCKs/YABQVKl2jf//0CDtcay1tbW1tbW1tbX/x/oK4SuHvEAH0Iqz9gAFBUqXaNxQ7W1tbW1tbW1tf/H90CHBH4e9wAeQyLfmAAQFVpdsy1tbW1tbW1tbX+GP4Q7JS1gAi7vXefLap76YZrrrrrrrrrrrrrr/jD6aCgWgCCDllGFNSltDYECCtQBwBFD4C7IAOBgj0uPdstQdBf9Drh7h+gn8GUYtnnnFu5hSHuDQA8AAQAwqAXD8IwxjmGKcQhJmNkMORBe1tbW1tbW1tclg4YOEumHAAO5EAECkzqXKcTv7hxIPrgNou04w/+HzEiY0gQsFE7q4AuwrxK0twnSSfgbgzAAEBAeQhZB1eAE0oAMEfewwhqn9YAwdQrCfarXujfB/yDhwrhSN47o3Tv8GHSDtLS0tLS0tLS/krN7DoKBgAB9LAAgFJnc8xTC7QNQgvH98SnjU9/6sYjOF2mRaxR//9ngZQpEfrAQJI7rK9JgBQ+XA4qLGe60fx7QcY4DeMvatDWTj/wf3APl/QT8AFyDlhLlOynajKQNwGKAAY+wY4g6v5A4KaUEK5j2xHCP8GHSDIoABG1tbW1tbW1tclNlEJOMumGgAEIS/e4E7nbESHKM+QbphteZpq4cxvBJ4Bxi8Zz4t1Ze4zAXxXqgACAOZsG1/94jbCBTzCaZZ03oahYAAgL0DULs/DL4UKCoJShdoVx88wSrguLJs1EPcWGHoyDsBimIpiFr/+3/aDYoABC1tbW1tbW1tfyRw9Q0KDHABQ/hOW+2OW4ABYfbDNjaD7mnH+94wNKEEL/IwxnRDX+EAQQKwla1namaxP3kQGxWRzAAZrkqBd9JRhhJevDBEln+8QSUYbVdKfuYOKHPDn/b+gn8M5OY7pZTYRrQ2BPAAEA4AEIocbP4CGzcVyYxsrzP4MOkGRQACFra2tra2tra1111111111111111311111111111111111111311111111131111111111311111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111114AAAAKrQZo4HeAK+P5+BLwERmGH/9ArDAw1+MVwOvlh18vxiWol+ngQQxsCDAYMgUACtizEQjNPf/AITzDxhD/DQneAKAu7Ahx4FG5/8MDpDW4NTlgT1+WZEiuAypZtabKruA22CUUB1bUOra+HVtQ6tr/TG4D/+CvAcIpDZbgZMBA0OFXjx04WMc47pf1ARFnBSdREuiXcmiO6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\" type=\"video/mp4\">\n", + " Your browser does not support the video tag.\n", + "</video>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from scipy.misc import imread\n", - "grid = imread('material/GosperGliderGun.png',flatten=True).astype(int)\n", + "from imageio import imread\n", + "grid = imread('material/GosperGliderGun.png', as_gray=True).astype(int)\n", "grid[grid>0] = ALIVE # values are from 0 to 255 - set everything nonzero to ALIVE\n", "ani = makeImshowAnimation(grid, gameOfLifeSweep, frames=6*15)\n", "displayAsHtmlVideo(ani, fps=15)" @@ -190,65 +269,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 2: Going parallel with ipyparallel and waLBerla" + "## Step 2: Demonstration with waLBerlas python bindings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "waLBerla is parallelized using MPI (message passing interface). That means that multiple processes are started, possibly on different machines which all execute the same program and communicate by sending messages. In a normal Python environment we would start multiple Python interpreters all executing the same script using `mpirun`. The following command would run a script using 4 processes:\n", + "waLBerla is parallelized using MPI (message passing interface). That means that multiple processes are started, possibly on different machines which all execute the same program and communicate by sending messages. In a typical Python environment, we would start multiple Python interpreters all executing the same script using `mpirun`. The following command would run a script using four processes:\n", "\n", "\n", "``` mpirun -np 4 python3 my_waLBerla_script.py```\n", "\n", - "\n", - "\n", - "To make use of multiple processes in this IPython environment we use the [ipyparallel](http://ipyparallel.readthedocs.org/en/latest/intro.html) package. \n", - "Therefore you first have to [start an ipyparallel cluster here in the IPython Cluster tab](/tree/ipython-tutorials#ipyclusters). Otherwise the following code will not work.\n", - "\n", - "Now lets test our parallel environment. After importing and initializing *ipyparallel* we only have to \n", - "prefix a code cell with the IPython magic **px** and the code cell will be executed by each process of the cluster." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from ipyparallel import Client\n", - "rc = Client()\n", - "numberOfProcesses = len(rc.ids)\n", - "print(\"There are %d processes in your ipyparallel cluster\" % ( numberOfProcesses,))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px\n", - "print(\"Hello parallel world\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If above cells produce errors, go back to the IPCluster setup to make sure your cluster is running. If everything works you see the hello world message printed by each process in the cluster." + "Using multiple processes is not very convenient in an IPython environment. Thus we only demonstrate here with one process. However, this tutorial can be easily extended to multiple processes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now lets implement the *Game of Life* algorithm in parallel using waLBerla. waLBerla divides the complete domain into blocks. These blocks can then be distributed to the participating processes. We will set up here a simple example where each process gets one block. waLBerla can put multiple blocks per process. This makes sense if the computational load varies for each block, then a process receives either few expensive blocks or many cheap blocks.\n", + "Now lets implement the *Game of Life* algorithm in using waLBerla. waLBerla divides the complete domain into blocks. These blocks can then be distributed to the participating processes. We will set up here a simple example where each process gets one block. waLBerla can put multiple blocks per process. This makes sense if the computational load varies for each block, then a process receives either few expensive blocks or many cheap blocks.\n", "\n", "While blocks are the basic unit of load balancing they also act as container for distributed data. In this *Game of Life* example we have to distribute our grid to the blocks e.g. each block internally stores only part of the complete domain. \n", "The local grids are extended by one ghost layer which are a shadow copy of the outermost layer of the neighboring block.\n", @@ -261,65 +301,57 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 8, + "metadata": {}, "outputs": [], "source": [ - "from scipy.misc import imread \n", + "from imageio import imread\n", "import os\n", "\n", "# Read the initial scenario\n", - "initialConfig = np.rot90( imread('material/GosperGliderGun.png',flatten=True).astype(int), 3 )\n", - "initialConfig[initialConfig>0] = ALIVE # values are from 0 to 255 - set everything nonzero to ALIVE\n", - "\n", - "# and send it to all execution engines together with other local variables\n", - "rc[:].push( {'initialConfig' : initialConfig,\n", - " 'numberOfProcesses' : numberOfProcesses,\n", - " 'ALIVE' : ALIVE,\n", - " 'DEAD' : DEAD, \n", - " 'neighborhoodD2Q9' : neighborhoodD2Q9,\n", - " 'gameOfLifeSweep' : gameOfLifeSweep,\n", - " 'cwd' : os.getcwd() } )\n", - "pass" + "initialConfig = np.rot90( imread('material/GosperGliderGun.png',as_gray=True).astype(int), 3 )\n", + "initialConfig[initialConfig>0] = ALIVE # values are from 0 to 255 - set everything nonzero to ALIVE" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Block on rank 0: with offset (0, 0, 0)\n" + ] + } + ], "source": [ - "%%px\n", + "# %%px\n", "import sys\n", "import waLBerla as wlb\n", "import numpy as np\n", "import os\n", "\n", - "os.chdir( cwd ) # change working directory of all engines to working directory of notebook kernel\n", + "# For this tutorial we use only one process. The code can easaly run with more processes and mpirun as pyhton script\n", + "numberOfProcesses = 1\n", "\n", "domainSize = (initialConfig.shape[0], initialConfig.shape[1], 1)\n", "\n", "# We can either specify the detailed domain partitioning ...\n", - "blocks = wlb.createUniformBlockGrid(cellsPerBlock=(domainSize[0]//numberOfProcesses, domainSize[1], domainSize[2]),\n", - " blocks=(numberOfProcesses,1,1), \n", + "blocks = wlb.createUniformBlockGrid(blocks=(numberOfProcesses, 1, 1),\n", + " cellsPerBlock=(domainSize[0]//numberOfProcesses, domainSize[1], domainSize[2]),\n", " periodic=(1,1,1))\n", "\n", - "# ... or let waLBerla do it automatically - \n", - "# if the domainSize is not divisible by the block count the domain is slightly extended\n", - "blocks = wlb.createUniformBlockGrid(cells = domainSize, periodic=(1,1,1))\n", "\n", "# Now put one field (i.e. grid) on each block\n", - "wlb.field.addToStorage(blocks, name='PlayingField', type=np.int, ghostLayers=1)\n", + "wlb.field.addToStorage(blocks, name='PlayingField', dtype=np.int, ghostLayers=1)\n", "\n", "# Iterate over local blocks - in our setup we have exactly one block per process - but lets be general\n", "for block in blocks:\n", - " offsetInGlobalDomain = blocks.transformLocalToGlobal(block, (0,0,0))\n", + " offsetInGlobalDomain = blocks.transformLocalToGlobal(block, wlb.Cell(0,0,0))\n", " myRank = wlb.mpi.rank()\n", - " print(\"Block on rank %d: with offset %s\" % (myRank, offsetInGlobalDomain ))" + " print(\"Block on rank %d: with offset %s\" % (myRank, offsetInGlobalDomain[:] ))" ] }, { @@ -331,13 +363,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2cAAAJ6CAYAAACsdDqUAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAZtElEQVR4nO3df6jdd53n8dd7kw6WdcA4SUtoaztIWabImjK3RXD+cNRZOu6w1T+EKYz0D6H+MQEFl6XrP6MLC/PHqPtHBqGuxbDrOhR01iLuj9JVXIehJu3U2lKHinScamiSNaL+49D63j/uEUNNvDf3npvz9p7HAy7nnO/5nnPeufkk5JnvOd9b3R0AAABW65+tegAAAADEGQAAwAjiDAAAYABxBgAAMIA4AwAAGECcAQAADHDwar7Y4cOH++abb7maLwkAADDGE088fr67j1zqvqsaZzfffEv+5rHTV/MlAQAAxrj2mvqHy93nbY0AAAADiDMAAIABxBkAAMAAW8ZZVb2qqr5eVd+oqmeq6iOL7R+uqu9V1ZOLr3fs/bgAAAD703ZOCPLTJG/t7p9U1TVJvlZV/2Nx38e7+y/2bjwAAID1sGWcdXcn+cni5jWLr97LoQAAANbNtj5zVlUHqurJJGeTPNLdjy3uOl5VT1XVg1V1aM+mBAAA2Oe2FWfd/XJ3H0tyY5I7q+oNST6R5PVJjiU5k+Sjl3psVd1XVaer6vS58+eWNDYAAMD+ckVna+zuHyb5SpK7uvvFRbT9LMknk9x5mcc80N0b3b1x5PAlfxA2AADA2tvO2RqPVNVrFtevTfL2JN+qqqMX7fauJE/vzYgAAAD733bO1ng0ycmqOpDNmHuou79YVf+lqo5l8+Qgzyd5396NCQAAsL9t52yNTyW5/RLb37MnEwEAAKyhK/rMGQAAAHtDnAEAAAwgzgAAAAbYzglBAGBfOHTH8V0/x4VTJ5YwCQD8MkfOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAxwcNUDALAeDt1xfNfPceHUiSVMAgAzOXIGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwAAHVz0AsFyH7ji+6hFy4dSJVY/ARSasiWTGupgwAwBcjiNnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAY4uOoBgF84dMfxXT/HhVMnljDJ6vleAOtmGX/v7Za/N2G1HDkDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMMDBVQ8A+8WhO47v+jkunDqxhEn2h2V8L3b7e+L3AwC4mhw5AwAAGECcAQAADCDOAAAABtgyzqrqVVX19ar6RlU9U1UfWWx/bVU9UlXPLS4P7f24AAAA+9N2jpz9NMlbu/uNSY4luauq3pTk/iSPdvetSR5d3AYAAGAHtoyz3vSTxc1rFl+d5O4kJxfbTyZ5555MCAAAsAa29ZmzqjpQVU8mOZvkke5+LMn13X0mSRaX1+3dmAAAAPvbtuKsu1/u7mNJbkxyZ1W9YbsvUFX3VdXpqjp97vy5nc4JAACwr13R2Rq7+4dJvpLkriQvVtXRJFlcnr3MYx7o7o3u3jhy+MguxwUAANiftnO2xiNV9ZrF9WuTvD3Jt5I8nOTexW73JvnCXg0JAACw3x3cxj5Hk5ysqgPZjLmHuvuLVfW3SR6qqvcm+W6Sd+/hnAAAAPvalnHW3U8luf0S2/9fkrftxVAAAADr5oo+cwYAAMDeEGcAAAADiDMAAIABxBkAAMAA2zlb4yiH7ji+6hFy4dSJVY8Aoy3jz+mEP2f75dcxYYZkxvdzwgxwOdYW4MgZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAMcXPUAwP5z4dSJVY+wFPvl1zGF7ycA/GqOnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYoLr7qr3Y7/7uRv/NY6ev2uvBr5tDdxzf9XNcOHViCZOsnu8FALAfXXtNPd7dG5e6z5EzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAMcXPUAwC9cOHVi189x6I7jS5hkd5bx61jGcwAA/Dpx5AwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAAdXPQCwXBdOnVj1CAAA7IAjZwAAAAOIMwAAgAHEGQAAwABbxllV3VRVX66qZ6vqmap6/2L7h6vqe1X15OLrHXs/LgAAwP60nROCvJTkg939RFX9ZpLHq+qRxX0f7+6/2LvxAAAA1sOWcdbdZ5KcWVz/cVU9m+SGvR4MAABgnVzRZ86q6pYktyd5bLHpeFU9VVUPVtWhJc8GAACwNrYdZ1X16iSfS/KB7v5Rkk8keX2SY9k8svbRyzzuvqo6XVWnz50/t4SRAQAA9p9txVlVXZPNMPtMd38+Sbr7xe5+ubt/luSTSe681GO7+4Hu3ujujSOHjyxrbgAAgH1lO2drrCSfSvJsd3/sou1HL9rtXUmeXv54AAAA62E7Z2t8c5L3JPlmVT252PahJPdU1bEkneT5JO/bkwkBAADWwHbO1vi1JHWJu760/HEAAADW0xWdrREAAIC9Ic4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAQ6uegB27tAdx3f1+AunTixpEgAAYLccOQMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABjg4KoHYOcunDqx6hEAAIAlceQMAABgAHEGAAAwgDgDAAAYQJwBAAAMsGWcVdVNVfXlqnq2qp6pqvcvtr+2qh6pqucWl4f2flwAAID9aTtHzl5K8sHu/p0kb0ryp1V1W5L7kzza3bcmeXRxGwAAgB3YMs66+0x3P7G4/uMkzya5IcndSU4udjuZ5J17NSQAAMB+d0WfOauqW5LcnuSxJNd395lkM+CSXLfs4QAAANbFtuOsql6d5HNJPtDdP7qCx91XVaer6vS58+d2MiMAAMC+t604q6prshlmn+nuzy82v1hVRxf3H01y9lKP7e4HunujuzeOHD6yjJkBAAD2ne2crbGSfCrJs939sYvuejjJvYvr9yb5wvLHAwAAWA8Ht7HPm5O8J8k3q+rJxbYPJfnzJA9V1XuTfDfJu/dmRAAAgP1vyzjr7q8lqcvc/bbljgMAALCeruhsjQAAAOwNcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADDAlnFWVQ9W1dmqevqibR+uqu9V1ZOLr3fs7ZgAAAD723aOnH06yV2X2P7x7j62+PrScscCAABYL1vGWXd/NckPrsIsAAAAa2s3nzk7XlVPLd72eGhpEwEAAKyhncbZJ5K8PsmxJGeSfPRyO1bVfVV1uqpOnzt/bocvBwAAsL/tKM66+8Xufrm7f5bkk0nu/BX7PtDdG929ceTwkZ3OCQAAsK/tKM6q6uhFN9+V5OnL7QsAAMDWDm61Q1V9NslbkhyuqheS/FmSt1TVsSSd5Pkk79vDGQEAAPa9LeOsu++5xOZP7cEsAAAAa2s3Z2sEAABgScQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABggC3jrKoerKqzVfX0RdteW1WPVNVzi8tDezsmAADA/radI2efTnLXK7bdn+TR7r41yaOL2wAAAOzQlnHW3V9N8oNXbL47ycnF9ZNJ3rnkuQAAANbKTj9zdn13n0mSxeV1yxsJAABg/ez5CUGq6r6qOl1Vp8+dP7fXLwcAAPBraadx9mJVHU2SxeXZy+3Y3Q9090Z3bxw5fGSHLwcAALC/7TTOHk5y7+L6vUm+sJxxAAAA1tN2TqX/2SR/m+RfVNULVfXeJH+e5A+q6rkkf7C4DQAAwA4d3GqH7r7nMne9bcmzAAAArK09PyEIAAAAWxNnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAQ7u5sFV9XySHyd5OclL3b2xjKEAAADWza7ibOH3u/v8Ep4HAABgbXlbIwAAwAC7jbNO8r+r6vGqum8ZAwEAAKyj3b6t8c3d/f2qui7JI1X1re7+6sU7LKLtviS56XWv2+XLAQAA7E+7OnLW3d9fXJ5N8tdJ7rzEPg9090Z3bxw5fGQ3LwcAALBv7TjOquqfV9Vv/vx6kn+V5OllDQYAALBOdvO2xuuT/HVV/fx5/lt3/8+lTAUAALBmdhxn3f2dJG9c4iwAAABry6n0AQAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA4gzAACAAcQZAADAAOIMAABgAHEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA4gwAAGAAcQYAADCAOAMAABhAnAEAAAwgzgAAAAYQZwAAAAOIMwAAgAHEGQAAwADiDAAAYABxBgAAMIA4AwAAGECcAQAADCDOAAAABhBnAAAAA+wqzqrqrqr6+6r6dlXdv6yhAAAA1s2O46yqDiT5yyR/mOS2JPdU1W3LGgwAAGCd7ObI2Z1Jvt3d3+nuf0ryV0nuXs5YAAAA62U3cXZDkn+86PYLi20AAABcod3EWV1iW//STlX3VdXpqjp97vy5XbwcAADA/rWbOHshyU0X3b4xyfdfuVN3P9DdG929ceTwkV28HAAAwP61mzg7leTWqvrtqvqNJH+c5OHljAUAALBeDu70gd39UlUdT/K/khxI8mB3P7O0yQAAANbIjuMsSbr7S0m+tKRZAAAA1taufgg1AAAAyyHOAAAABhBnAAAAA4gzAACAAar7l35u9N69WNW5JP/wK3Y5nOT8VRoHrpT1yVTWJpNZn0xlbbIqN3f3JX8A9FWNs61U1enu3lj1HHAp1idTWZtMZn0ylbXJRN7WCAAAMIA4AwAAGGBanD2w6gHgV7A+mcraZDLrk6msTcYZ9ZkzAACAdTXtyBkAAMBaGhNnVXVXVf19VX27qu5f9Tysr6p6sKrOVtXTF217bVU9UlXPLS4PrXJG1lNV3VRVX66qZ6vqmap6/2K79cnKVdWrqurrVfWNxfr8yGK79ckIVXWgqv6uqr64uG1tMs6IOKuqA0n+MskfJrktyT1Vddtqp2KNfTrJXa/Ydn+SR7v71iSPLm7D1fZSkg929+8keVOSP138XWl9MsFPk7y1u9+Y5FiSu6rqTbE+meP9SZ696La1yTgj4izJnUm+3d3f6e5/SvJXSe5e8Uysqe7+apIfvGLz3UlOLq6fTPLOqzoUJOnuM939xOL6j7P5j4wbYn0yQG/6yeLmNYuvjvXJAFV1Y5J/neQ/X7TZ2mScKXF2Q5J/vOj2C4ttMMX13X0m2fwHcpLrVjwPa66qbklye5LHYn0yxOJtY08mOZvkke62PpniPyX5d0l+dtE2a5NxpsRZXWKb00gCXEJVvTrJ55J8oLt/tOp54Oe6++XuPpbkxiR3VtUbVj0TVNUfJTnb3Y+vehbYypQ4eyHJTRfdvjHJ91c0C1zKi1V1NEkWl2dXPA9rqqquyWaYfaa7P7/YbH0ySnf/MMlXsvn5XeuTVXtzkn9TVc9n86Mzb62q/xprk4GmxNmpJLdW1W9X1W8k+eMkD694JrjYw0nuXVy/N8kXVjgLa6qqKsmnkjzb3R+76C7rk5WrqiNV9ZrF9WuTvD3Jt2J9smLd/e+7+8buviWb/8b8P939J7E2GWjMD6Guqndk8/3AB5I82N3/ccUjsaaq6rNJ3pLkcJIXk/xZkv+e5KEkr0vy3STv7u5XnjQE9lRV/V6S/5vkm/nF5yY+lM3PnVmfrFRV/ctsnlThQDb/8/eh7v4PVfVbsT4ZoqrekuTfdvcfWZtMNCbOAAAA1tmUtzUCAACsNXEGAAAwgDgDAAAYQJwBAAAMIM4AAAAGEGcAAAADiDMAAIABxBkAAMAA/x8Qj+b4bp1m3AAAAABJRU5ErkJggg==\n", + "text/plain": [ + "<Figure size 1080x864 with 1 Axes>" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ - "%%px\n", "import waLBerla.plot as wlbPlt\n", "\n", "import matplotlib\n", @@ -350,35 +392,49 @@ "# Initialize our grid\n", "for block in blocks:\n", " grid = wlb.field.toArray( block['PlayingField'] ).squeeze()\n", - " offsetInGlobalDomain = blocks.transformLocalToGlobal(block, (0,0,0))\n", + " offsetInGlobalDomain = blocks.transformLocalToGlobal(block, wlb.Cell(0,0,0))\n", " blockSize = grid.shape[:2]\n", " xBegin, xEnd = offsetInGlobalDomain[0], offsetInGlobalDomain[0] + grid.shape[0]\n", " yBegin, yEnd = offsetInGlobalDomain[1], offsetInGlobalDomain[1] + grid.shape[1]\n", " grid[:,:] = initialConfig[xBegin:xEnd, yBegin:yEnd]\n", "\n", - "wlbPlt.scalarField( blocks, 'PlayingField', wlb.makeSlice[:,:,0] )" + "wlbPlt.scalar_field( blocks, 'PlayingField', wlb.makeSlice[:,:,0] )" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<video controls width=\"80%\">\n", + " <source src=\"data:video/x-m4v;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQABOidtZGF0AAACcgYF//9u3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MCByMzAxMSBjZGU5YTkzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTAgcmVmPTIgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTI0IGxvb2thaGVhZF90aHJlYWRzPTQgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAXkGWIhAvyYoAAqcScnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddf+uFWMXsNcABGPxxRmZUtRbuGf1QkOMehppXZgvn+AgZkRHlLaR3jZ4RRp2TCS3PvqSJANmyEAo+zOC8FRE2J9mMWf9wwMxP6AspOW+L/+82Y1yM6qQR3O/GZjkhSnc/hHD/LgAJkZhOhyDCd4w8yGGYAAIAwDEkS3KhgAMBynwErJVJS7LDBTS2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra4+cP+H8ABILNCIgjz6RZPwdMNhrrrrrrrrrrrrrvrpxQBvvvvvvvvvvvvvvvvvvvvvv/iH/gsBQA4RCATLfEAE0QL59/+f/4LIAQKRj6LT0//+GVQ18+rJC9yMFN9999999999999999999999999QTOmCeuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrr+A//hoFEAHeyRjRl1e95WXwJARAYjuF2WWHJq/xD/0CwgHCCoZfA8TgSKwZKX4l3gGIf+gWQA5NMMVFh+HK/uBIkBU8Gpy9xHf5//ho78AA5tJtU+v+IUw18nZc5IE3dMEdS11111111111111111111111109dddddddddddddddPXXXXXXXXXXXxD//BWCQDgIDBbL4iABIYAARQXZZOy/JpNwuhsxkxnMZMZ/9X6fchkhnnD/+wVmAArlGaIx7Rfgw5J5IQpubzwqHa6666/gP/4KwXQEKNmetXwcQok5YH0duak00PDAP/QaPAFYvDiLA+2Hf4D4FBKDCRgwlLXwde8AxD/0CyBwQZrL4EgkAojwOmcvxHeAbR/6BZADKqSSWr3/AW4xL9viHD/2CwFIOAevJ45S8HD3wcPfA4jS5DiNLieuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrp666666666/+H/BYCKABEFuIpj6J4Nmt5YdY4f/oFZgavGgUOAxLol3iOiO+IBAP/grgOCYfZb4eAONAKEdEd22+v//YKzgAzCucun/77/z154daeoI6666/gMP/DQKIATaLGrIq7X/rAkEwNHDL490mbmqZofj//BXgBrRiVle7z5EOzMdfx9PMzG56SC2np66lrrrrrrr4h//grDwADdMmCZ8hQ53FgoLDBoBIeADhKA1TwdJ25gobjjrIbQp6w//sFYRAATyOIh1IY4iUhhwLJCLgE9PZZ5ERLmBC0897KdzUM11111111111109ddddddddddddf/8FXhUMQAEYaxxR2aUtRr+m/GzwRRpuRCavFbCGGIvjySTKQf/fAIQMSyiKbpS2HzXIUDrqq7Mjld/8whDxdcBvsLD//hfYsP4ADAcp0A1dKpMbZ6WmF66666666//+HBYCbgcECC0BpcMvpW7lZocfH/QLBHAcRF6Y/6pp8Xh/LB4OIiQti3tt5mC0UBiACs/gGAf6BUCAQkhFoSTIkmcVYq/h//grFnY4DoAQx99+oZr/xw/grBVxQABALgZYIEAxvi1nLGOdGXxaD4YAZYiECqByzIOWZwdIyDpGdr4jgEP4Kw1wOECsQBkIsNZRZ21PJkl3McRS1th8B//gr8HBAikMdQ9wkObZW7mbzQp6eoJaenrqWuuuuuuuniOjjTBQ6gprrrrrrrrrrrrp6666666666666+u7//CQU+SITMCoRnKCTeCMOwxe1j2kJSxN98RQynmg7zWt8NRReTP+PgGwWFwHAAJAMHARlsILhSAhIEeDw94kO7Xx5/Dw4bAAYpShI5+QvgYMXA5WDLYgHcYuCTueCYfrrrrrp5mCrKZgpFAxAZGAANf/h/hsFWABndNoXYhAUMkhlfxBBISNNiHX00/n//YbLgBgKZ5H6f/92Kuv1BX8fAP4bBVgARVc0Qf2MUasE/AwsCDw43xzp7DTTX44BD+CsuoDgI5YdLBhQWG5RR3EHnTyxizEOivFoafI59pr4n8P4bCHACOLRRleXnEFIS0M9nMLdf4f+w0F42g9/jrSsoO7EHdj54BD9BoMcAGZV0+zfDgfoAlXof3q38FwCH6G+AA2bI3lGN2jYxTGWcImyCVODx6/Fv4BiH+gWEwHEZQSWwhJMK+iQ7xV/AM//BZwAzuimrT/8R2Rie7t9PTBbUtdddddddeEwwD/grD2AAolINGhlFtMDdgwF4OKhOBqnh3goNxw72uEv/8FYsrMAB4RoUp2IxRV78GELMwIoAS0S0LP+BOL3nv+mFa666666666666euuuuuuuuuuuuuuunm4+mYLNUwV1111109PT1+YYB/wVguwAwDJb2u7/iXNFsT3bb+3/9grE4Azu/97/f17/UFPoGAf8FYJsAEK1rvv/ngwVi6C9jPO8a4538BwCH8FfgOBGKD5YMFTQyhxfOIOHThYy0xPRfOhYhzN88Ahj48ZwAGSKJib1a5ZGPvwOFChrFAo/9HnTzDRiHRX8H//grJwAwrm3tdoB2mpl+/9sFP0wCP6Ggm4Ay+73v9/L794SmgCYnvb+AYh4dAs4DhMMPlsIL/BKnB494k78AzD/hzgAUkpMTerXf//gwYa4HkwJYQ6T/5Tb+AbQ/wWcAZ3/97/f/T++30wV1111111110tLa111111111111109ddddddddddddddf4RD/gsDGDgAFAABAFIwlBFAkFIHmFL8Gzl9r4wt/4LDYADKclD7vFK4Y1RABiUtkODbl30w7XXXXX/CP/BYCDgCgmRgEFIAo3NYS2m80PgGAfDQLBXAFE0whUAfbDlqauXW8XICnlgxxRJoul3m026YLv/D/BWDLABzjNEMNxer25BEAREIQ0FmWTMt8M2m1fn//YbE4AFcozRGaUbwYyv68kOeFMENr8cA/4KwUcHCBSIMXB1QjtBLl+bl3OiPF9v/4K/ADFe3t6vHKGiP5/zQVg4ABEL0HJ13/4h/wWAkwcCApGBVgHhXL8u/ANvh4LI8gwBBNNs5mevHaS5c8u8zBPJV111111111111111111111111109ddddddddddddddczyRrXS1111110v//DgsBFH6QAV0kzPQsq5ZYdcXEPJBycR2bTb/CIYAH9gr4DhONktiAIiEC6Lpd//4f2CsSRmAApppGTYuiqHfyQ7zMmemCeultbWuuu1ta666666666666666666666666666euuuuuuuuuuuuv//+FQtwAG0RCFcrsULL6J/iKgaYNR4badLBmyUwABADTQALoSt58umF6666666666Wl/8AwDhEPCASMQRtrh/D+wV34Atzw5IUw/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT11111111111118ov/+Egt8kAF1KaAHgrG52w6TGEIYvxdwx69aIIZK8PcsMWvbP7//8P/+CL4ADSvEt9utmiX5swY6WoXrrrrrrrrrrrpaWlrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666euuv//iGC4FwAGPzSH5iOGsLCYwDgI9+v/9fBdABNy2KN+u78yDXbXumC+uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrp6664pgwmt7+Kdtta+mC2pa66666666666666666666666666666666666666666666euuuuuuuuuuuuuuunrrrp6YMHTBbUtddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXT111/h/gGC4FwAGbbfKfBjA6xXHerb/4Bv4LoAJPcxVk/3f8qYud1R126YL6666666666666666666666666666666666666666666666euuuuuuuuuuuuv5IrFH+EgxAAbSIRxEZiBJHD/woNr1s4TT2PrLCOgmoGbmHhNbUkEf74BI6BtbZKSxmW/xlxkAMVFHsGwplA9e9FAKvqeosc9fE44f/8Lw4YADStNRbdatNFzVlunS0wzXXXS2tddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXT11111111111111111111111111111111111111111111111111109dddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT11111111111111109dddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXT11111111111111111111111111111111111111111111111111109ddddddddddddf+uEAHsNcABJELflgChiOU4C+4CDQMZisO6wG8HaHtNk6Ww/bGgjqNyLPHLn/rb+RjSzOQfMTGDMLd2p1WF5/8/8P/+CQvgANK8S326WaJfmvG2aWoXrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrp6666666666666666666666666666666666666666666666666666euuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666666euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrr/xXgKI1hjgAMP4yDe+1KEvXZ7WgzDWWSc4k3aARMnTJtc/n14AAgDgDXduyQFWAfgA9uKJwMlP+kQx4oKMWRmI6s8bx2VyAEQVd2RzAzCVxeqzTtbP+15AiemIYxP//sEsABERmZmRkRERnh/DAAodhoJb7Vhoqfhh9LTDNdddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXfXT11111111111111111111111111111111111111111111111111109dddddddddddddddPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXT11111111111111109dddddddddddddddddddddddddddddddddddddddddddddddddddPXXXXXXXXXXXXXXXT11111111111111111111111111111111111111111111111111109ddddddddddddf//ECghCwACZEMroIzEvSxifIzERlOdSDKetXkZiIynP2GQtfeLBk4OSI7dBJAaynw/Db4JIcMAAj+CKkoazjaj/zqWoXrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666/yL/4fCmhOoz1or6c/StcEhxRbKEP8IPXpG8PH//YIoY8AA0AHA4gmfqYVrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666766euuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunrrrrrrrrrrrrrrrp6666666666666666666666666666666666666666666666666666euuuuuuuuuuuuuuunrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrp6666666666666///jBCEsACILcRTNkTsxGIHTwj6Sf75dMJ11111111111111111111111111111111111111111111111111109ddddddddddddf7Cn/h8LYoh2ph+Lkj+7zhj5M2wmNmw4LADkaF4roUmcROgTHOCnlPUKcrD1+hj/CAZ4YADDuEp1vlmCXpr+YAAWAHBzyZAk/Xu+PAALACg4rLUsACGUszACAL1aCCKX/wAYBwSWoAAAg4aZPvvvvvvvvhPAAQ3Z2ZndmZ2d/+zgAYylmYAQBerQRh+vvvvvvvvv/8f8EvgAJxD8/QAKESlVQj3//8EHLTRvvvvvvvvv/8f8M+AAnEPy9AAoRKlVCONU9fffffffff/4/4JfAASjF7cgAQInKqpHr77777777/4Y/hDPfAJm79v9TC9dddddddddddddf8f6QQUCnwId7iHJ/1wfAppGoDgMoTIXZABwwi/USy5XxEP3IdQ/f+gn8NxHq0sp6sU7lsDQDwAKKkFw+CIYIxRCFM7yKX//ww6QVFANra2tra2tra5LBwwcJdMNAAd0gCCtnfYhjd/cOSH/gbTcmY/+USSY0gRYc3dXAVQpwUqkxeQSoG4zAAEAYWRSCDq8APSgDCNu1MWhffrAMOgznrsq9Jvv/UMoqjG7KcNxO9/0g2KAbW1tbW1tbW1/JWD2DQUCwAGRAEDtm69yENtB8Q3m+h18i//1QyCUW6RfmP//U8G7hq20Dvis1U9k4Ax44FMkr0o4NtrQ4hQbTty+Crkn/g/uH5f0E/AF2FeJWluE7ST4DcGYAAgIDyEKIOr+QcOFcKRvHdG6d/gw6QXFANra2tra2tra5KbKASfLphoABEFecwC/OdJyKf5GaQXKNb/hnL4S4AYI+blBCSCbXgvFXgA7rh9f3eT8Q5Ll0iSJnQ0OAAIBISAuH5j8c4CQyosoN2pzL/HxRZfyVN//Bh6aPgEIjoRnp/+380g2KANra2tra2tra/kjh6hoUGOACj0EjnpDO4ABZ9IYs+A+jEl+9ph4wZ6z+/juT33ECEOxmVhtjaxfvUGiplYAZav4bPMCYI5cMRBH64hYwLL2ShHDvPd4c/7f0EygCZW5CullJjCtDYXgACAQAEdA42fwEJm0rkxS5TlfwYdIKigDa2tra2tra2tdddddddddddddddd9ddddddddd9dddddddddd9ddddddddd9dddddddddd9dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeAAAADAkGaOBfgCKOLDXABA7/+rv7MX7nx3oQH+6jWA14rwHAXzJkHAXzJkHAXzJgKhLDMNHgkcEx4I5TvBqficBEYQQSDDLIxQ/LXy/BmllNL9PAghhBbtgQYDBgigBlSREiWr3/7gEJ7wETHC9VAECZmAQQgqtX/VcGoQQoViISGAoS6Jd00/sREyQmSxMkOteY3AYf+CskByMjy2wdBQQDG+PdLeqa6gIiJOCtT6fcmk3T+heKAwULDt4haO8V2GACP1wd/nH/BUbHCH+gVBBKvTT4tgrjsfmCAYB/wVAqEVLFWKvTT/BMfAcEUlqZg4IpLUzBwRSWpneU2P/+CohFx/vjHfj2CmSvf9cuaH/+wVhaZTp7HY5Xyvo7C+fqAiZxiwOSrkOSrkHW+DreDI3/8OCwOcAJNGZkIZ67RvvCzc5oTY4XD+g19yXgBmrSVLtI7EOhEOvpk/i4KYGwQa8n+AiAETmC3/+CsEnAGd3/vf7+dVf87BTn5TQngH+hoLuADD+9XG+/vAzYC1EO9PmwHAIfxvgCdILGKgdio9afRYchwdVWin9VXNlWyw0X+Dj9wieCeA5EyJmBBVEo/exVMpVPqNjC//QLC4AYVzbbXa8AkBcUb3l+DLkwOCDCUEAnmWJXR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\" type=\"video/mp4\">\n", + " Your browser does not support the video tag.\n", + "</video>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "%%px\n", "communication = wlb.createUniformBufferedScheme( blocks, 'D2Q9')\n", "communication.addDataToCommunicate( wlb.field.createPackInfo( blocks, 'PlayingField') )\n", "\n", "def runTimestep():\n", " communication()\n", " for block in blocks:\n", - " grid = wlb.field.toArray( block['PlayingField'], withGhostLayers=True )[:,:,1,0]\n", + " grid = wlb.field.toArray( block['PlayingField'], with_ghost_layers=True )[:, :, 1]\n", " gameOfLifeSweep( grid )\n", " \n", "\n", - "ani = wlbPlt.scalarFieldAnimation( blocks, 'PlayingField', wlb.makeSlice[:,:,0], runFunction=runTimestep, frames=100 )\n", + "ani = wlbPlt.scalar_field_animation( blocks, 'PlayingField', wlb.makeSlice[:,:,0], run_function=runTimestep, frames=100 )\n", "displayAsHtmlVideo( ani, fps=30, show=(wlb.mpi.rank()==0) )" ] } @@ -399,9 +455,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 02 - LBM.ipynb b/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 02 - LBM.ipynb deleted file mode 100644 index 1ec91a5cc..000000000 --- a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 02 - LBM.ipynb +++ /dev/null @@ -1,254 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": false - }, - "source": [ - "# waLBerla Tutorial 02: LBM Setup\n", - "\n", - "In this tutorial we set up a basic lattice Boltzmann simulation of a periodic channel.\n", - "\n", - "First we create a simple block structure, same as in the last \"Game of Life\" tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from waLBerla import *\n", - "from material.matplotlib_setup import *\n", - "\n", - "blocks = createUniformBlockGrid( cells=(500,200,1), periodic=(1,0,1) )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For LBM we have the choice of different lattice models. A lattice model in waLBerla consists of:\n", - "- a stencil that defines the cell neighborhoods. In this tutorial we use a two dimensional D2Q9 stencil.\n", - "- a force model to use volume forces in the domain. For our setup we need a constant volume force to drive the channel and make use of the \"SimpleConstant\" model. \n", - "- a collision model defining the LBM collision operator. Her we use SRT (BGK), however the two relaxation time (TRT) model is also supported and for the D3Q19 model a MRT implementation is available as well\n", - "\n", - "With the lattice model a pdf field is created and added to all blocks. Additionally we create optional adaptors for velocity and density. These adaptors behave similar to fields, however no memory is required for them, the values are computed on-the-fly from the pdfs.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "omega = 1.3\n", - "collisionModel =lbm.collisionModels.SRT(omega)\n", - "forceModel = lbm.forceModels.SimpleConstant( (1e-5,0,0) )\n", - "latticeModel = lbm.makeLatticeModel(\"D2Q9\", collisionModel, forceModel)\n", - "lbm.addPdfFieldToStorage(blocks, \"pdfs\", latticeModel, \n", - " velocityAdaptor=\"vel\", densityAdaptor=\"rho\", initialDensity=1.0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To store geometry information a flag field is created and used as input to the LBM boundary handling:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "field.addFlagFieldToStorage( blocks, 'flags')\n", - "lbm.addBoundaryHandlingToStorage(blocks, 'boundary', 'pdfs', 'flags')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we define the setup of our channel. At the northern and southern domain borders no-slip boundaries are required.\n", - "To set up the boundaries the distributed nature of the domain has to be taken into account. \n", - "When iterating over the blocks we only get the locally stored blocks. So this loop is automatically parallelized when using multiple MPI processes. For each block we first check if it is located at the border of the global domain, and if so, we set the border slice to no-slip. When defining the slice, the additional __'g'__ marker is used, to place the boundary in the ghost layer.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def setBoundariesChannel( blocks, boundaryHandlingID ):\n", - " for block in blocks:\n", - " b = block[ boundaryHandlingID ]\n", - " if block.atDomainMinBorder[1]:\n", - " b.forceBoundary('NoSlip', makeSlice[ :, 0, :, 'g'])\n", - " if block.atDomainMaxBorder[1]:\n", - " b.forceBoundary('NoSlip', makeSlice[ :,-1, :, 'g'])\n", - " b.fillWithDomain()\n", - "setBoundariesChannel( blocks, 'boundary' )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally a LBM sweep functor is created, making use of the already created pdf and flag field." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "sweep = lbm.makeCellwiseSweep(blocks, \"pdfs\", flagFieldID='flags', flagList=['fluid'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since we have set our domain to be periodic in x-direction,the east and west boundary are taken care of by the ghost layer exchange. Here the communication is set up as explained in the previous tutorial. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "communication = createUniformBufferedScheme( blocks, 'D2Q9')\n", - "communication.addDataToCommunicate( field.createPackInfo( blocks, 'pdfs') )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One timestep consists of ghost layer communication, boundary handling and an LBM stream-collide step" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def run( timesteps ):\n", - " for t in range(timesteps):\n", - " communication()\n", - " for block in blocks: block['boundary']()\n", - " for block in blocks: sweep.streamCollide( block )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To get a more interesting result we place an airfoil shaped object inside our channel. This geometry is loaded from an image placed in the 'material' subfolder. You can upload different images and simulate the flow around these objects:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from waLBerla.geometry_setup import *\n", - "setBoundaryFromBlackAndWhiteImage(blocks, \"boundary\", makeSlice[0.25:0.75, 0.3:0.6 ,0.5], \n", - " \"material/wing.png\", \"NoSlip\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we create a video of the density distribution in the channel ( which is proportional to pressure in LBM). Before creating a frame for the video we let the simulation run for 10 timesteps, then we set the density inside the object to NaN to get a better looking plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def setByMask( blocks, targetField, targetValue, flagField, flagNames ):\n", - " for b in blocks:\n", - " mask = 0\n", - " for flagName in flagNames:\n", - " mask |= b[flagField].flag(flagName)\n", - "\n", - " targetArr = field.toArray( b[targetField], True )\n", - " flagArr = field.toArray( b[flagField] , True )[:,:,:,0]\n", - " targetArr[ np.bitwise_and( flagArr, mask ) > 0, : ] = targetValue\n", - "\n", - "\n", - " \n", - "def visualizationStep():\n", - " run(10)\n", - " # Set pdfs to NaN in NoSlip cells to better see the obstacle in the visualization:\n", - " setFieldUsingFlagMask(blocks, 'pdfs', np.NaN, 'flags', ['NoSlip'] )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import waLBerla.plot as wplt\n", - "\n", - "run(700) # run the simulation until flow field has stabilized - then no colormap rescaling is required\n", - "ani = wplt.scalarFieldAnimation( blocks, 'rho', makeSlice[:,:,0.5], visualizationStep, frames=300)\n", - "displayAsHtmlImage(ani)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.1" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 03 - LBM with extensions.ipynb b/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 03 - LBM with extensions.ipynb deleted file mode 100644 index e1787ea40..000000000 --- a/python/waLBerla_docs/ipython/ipython-tutorials/Tutorial 03 - LBM with extensions.ipynb +++ /dev/null @@ -1,272 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# waLBerla Tutorial 03: LBM Extensions\n", - "\n", - "\n", - "We start with the LBM simulation of an airfoil from last tutorial. In this tutorial we show how the boundary conditions can be set in a flexible way, by generating the airfoil geometry from an analytic description. Additionally we add some evaluation of force acting on the airfoil.\n", - "\n", - "## A) Programmatic Boundary setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from waLBerla import *\n", - "import waLBerla.plot as wplt\n", - "from waLBerla.geometry_setup import *\n", - "import numpy as np\n", - "import itertools\n", - "from IPython import display\n", - "from material.matplotlib_setup import *" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Instead of loading the airfoil from an image file, we use the [NACA analytical description](https://en.wikipedia.org/wiki/NACA_airfoil) to generate the geometry. The following function is just an implementation of the NACA airfoil description from Wikipedia. Additionally the airfoil can be rotated afterwards. The resulting array has value 1 in cells overlapped by the airfoil and zero otherwise." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def makeNacaAirfoil( length, thickness=30, angle=0 ):\n", - " import scipy\n", - " import scipy.ndimage \n", - " def nacaAirfoil(x, thickness,chordLength):\n", - " xOverC = x / chordLength\n", - " y_t = 0\n", - " coeffs = [ 0.2969, -0.1260, - 0.3516, 0.2843, -0.1015 ]\n", - " for coeff, exponent in zip( coeffs, [ 0.5, 1,2,3,4 ] ):\n", - " y_t += coeff * xOverC ** exponent\n", - " y_t *= 5 * thickness/100 * chordLength\n", - " return y_t\n", - "\n", - " domain = np.zeros( (length, int(length*thickness/100) ) )\n", - " it = np.nditer( domain, flags=['multi_index'], op_flags= ['readwrite'] )\n", - " while not it.finished:\n", - " x,y = it.multi_index\n", - " y -= domain.shape[1]/2\n", - " if abs(y) < nacaAirfoil( x, thickness, domain.shape[0] ):\n", - " it[0] = 1\n", - " it.iternext()\n", - " domain = np.rot90( domain,1 )\n", - " domain = scipy.ndimage.interpolation.rotate( domain, angle=-angle)\n", - "\n", - " domain[ domain > 0.5 ] = 1\n", - " domain[ domain <= 0.5 ] = 0\n", - " domain = domain.astype( np.int32 )\n", - " return domain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from ipywidgets import interactive\n", - "def showAirfoil( thickness=10, angle=20 ):\n", - " wplt.style.use('ggplot')\n", - " wplt.imshow( makeNacaAirfoil(300, thickness, angle) )\n", - "widgets = interactive(showAirfoil, thickness=(5,50,1), angle=(-45,45, 1))\n", - "display.display(widgets)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "airfoilArr = makeNacaAirfoil( length=200, **widgets.kwargs )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def setBoundariesChannel( blocks, boundaryHandlingID ):\n", - " for block in blocks:\n", - " b = block[ boundaryHandlingID ]\n", - " if block.atDomainMinBorder[1]:\n", - " b.forceBoundary( 'NoSlip', makeSlice[ :, 0, :, 'g'] )\n", - " if block.atDomainMaxBorder[1]:\n", - " b.forceBoundary( 'NoSlip', makeSlice[ :,-1, :, 'g'] )\n", - " b.fillWithDomain()\n", - "\n", - "\n", - "# Lattice Model Setup\n", - "omega = 1.7\n", - "domainSize = ( airfoilArr.shape[1]*2, airfoilArr.shape[0] * 2 )\n", - "blocks = createUniformBlockGrid( cells=(500,200,1), periodic=(1,0,1) )\n", - "collisionModel =lbm.collisionModels.SRT( omega )\n", - "forceModel = lbm.forceModels.SimpleConstant( (1e-5,0,0) )\n", - "latticeModel = lbm.makeLatticeModel( \"D2Q9\", collisionModel, forceModel )\n", - "lbm.addPdfFieldToStorage( blocks, \"pdfs\", latticeModel, velocityAdaptor=\"vel\", densityAdaptor=\"rho\", initialDensity=1.0 )\n", - "field.addFlagFieldToStorage( blocks, 'flags' )\n", - "lbm.addBoundaryHandlingToStorage( blocks, 'boundary', 'pdfs', 'flags' )\n", - "\n", - "# Boundary Setup\n", - "setBoundaryFromArray( blocks, 'boundary', makeSlice[0.3:0.7, 0.3:0.7 ,0.5], airfoilArr, { 1: 'NoSlip' }, resizeFunc=binaryResize )\n", - "setBoundariesChannel( blocks, 'boundary' )\n", - "\n", - "sweep = lbm.makeCellwiseSweep( blocks, \"pdfs\", flagFieldID='flags', flagList=['fluid'] )\n", - "\n", - "# Communication\n", - "communication = createUniformBufferedScheme( blocks, 'D3Q19')\n", - "communication.addDataToCommunicate( field.createPackInfo( blocks, 'pdfs') )\n", - "\n", - "def run( timesteps=10 ):\n", - " for t in range(timesteps):\n", - " communication()\n", - " for block in blocks: block['boundary']()\n", - " for block in blocks: sweep.streamCollide( block )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "run(100)\n", - "setFieldUsingFlagMask(blocks, 'pdfs', np.NaN, 'flags', ['NoSlip'] )\n", - "ani = wplt.scalarFieldAnimation( blocks, 'rho', makeSlice[:,:,0.5], runFunction=run )\n", - "displayAsHtmlImage( ani )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## B) Evaluation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "class ForceCalculationMasks:\n", - " @staticmethod\n", - " def addToBlock(block, blockStorage):\n", - " pdfFieldArr = field.toArray(block['pdfs'])\n", - " flagFieldArr = field.toArray(block['flags'])[:,:,:,0]\n", - " directions = block['pdfs'].latticeModel.directions\n", - " maskArr = np.zeros( pdfFieldArr.shape, dtype=bool )\n", - " pdfDirectionArr = np.zeros( list(pdfFieldArr.shape) + [3] )\n", - "\n", - " fluidFlag = block['flags'].flag(\"fluid\")\n", - " noSlipFlag = block['flags'].flag(\"NoSlip\")\n", - "\n", - " innerPartOfDomain = itertools.product( range(2, maskArr.shape[0]-2),\n", - " range(2, maskArr.shape[1]-2),\n", - " range(0, maskArr.shape[2] ) )\n", - "\n", - " for x,y,z in innerPartOfDomain:\n", - " if flagFieldArr[x,y,z] & fluidFlag:\n", - " for dirIdx, dir in enumerate(directions):\n", - " nx, ny, nz = x+dir[0], y+dir[1], z+dir[2]\n", - " if flagFieldArr[nx,ny,nz] & noSlipFlag:\n", - " maskArr[x,y,z,dirIdx ] = True\n", - " pdfDirectionArr[x,y,z,:] = dir\n", - " return ForceCalculationMasks( maskArr, pdfDirectionArr )\n", - "\n", - " def __init__(self, maskArr, pdfDirectionArr):\n", - " self._maskArr = maskArr\n", - " self._pdfDirectionArr = pdfDirectionArr\n", - "\n", - " def calculateForceOnBoundary(self, pdfField):\n", - " force = np.array([ 0.0 ] * 3)\n", - " pdfFieldArr = field.toArray( pdfField )\n", - " for i in range(3):\n", - " fArr = pdfFieldArr[ self._maskArr ] * self._pdfDirectionArr[self._maskArr,i]\n", - " force[i] += np.sum( fArr )\n", - " return force\n", - "\n", - "def calculateForceOnBoundary( blocks ):\n", - " force = np.array( [ 0.0 ] * 3 )\n", - " for block in blocks:\n", - " force += block['ForceCalculation'].calculateForceOnBoundary(block['pdfs'])\n", - " return np.array( mpi.reduceReal( force, mpi.SUM ) )\n", - "\n", - "blocks.addBlockData('ForceCalculation', ForceCalculationMasks.addToBlock)\n", - "pass" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "lifts = []\n", - "\n", - "for i in range(100):\n", - " run( 10 ) \n", - " plt.subplot(2,1,1)\n", - " wplt.scalarField( blocks, 'rho', makeSlice[:,:,0.5] )\n", - " plt.subplot(2,1,2)\n", - " f = calculateForceOnBoundary( blocks )\n", - " lifts.append( f[1] )\n", - " wplt.plot( lifts, color='b' )\n", - " wplt.ylim([0,0.15])\n", - " wplt.xlim(0 , 100 )\n", - " wplt.title(\"Lift\")\n", - " display.display( wplt.gcf() )\n", - " display.clear_output(wait=True) " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.1" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/python/waLBerla_docs/modules/blockforest.rst b/python/waLBerla_docs/modules/blockforest.rst index f2ce559e6..ccd463bae 100644 --- a/python/waLBerla_docs/modules/blockforest.rst +++ b/python/waLBerla_docs/modules/blockforest.rst @@ -8,21 +8,23 @@ Reference ========= -.. py:function:: createUniformBlockGrid(cells, cellsPerBlock, blocks, periodic=(0,0,0), dx=1.0, oneBlockPerProcess=True) +.. py:function:: createUniformBlockGrid(blocks, cellsPerBlock, dx=1, oneBlockPerProcess=True, periodic=(0,0,0), keepGlobalBlockInformation=False) - Creates a new uniform StructuredBlockStorage. Similar to cpp function createUniformBlockGridFromConfig. - Specify either cells or (cellsPerBlock and blocks). - - :param cells: 3-tuple with total numbers of cells in x,y,z direction. The returned BlockStorage may have - more cells if the cell count in a dimension is not divisible by the number of processes. - If this parameter is set, cellsPerBlock and blocks must not be set. - :param cellsPerBlock: 3-tuple with total number of cells per block in x,y,z direction. - If this parameter is set, also blocks has to be set, but not cells - :param blocks: 3-tuple with total number of blocks in x,y,z direction. - When using this parameter you also have to pass cellsPerBlock. - :param periodic: Periodicity of the domain in x,y,z direction - :param dx: Side length of a single cell. - :param oneBlockPerProcess: If True, each process gets one block. If False, all blocks are put to one process. - The second option makes only sense for debugging or testing. + Creates a new uniform StructuredBlockForest. Similar to cpp function blockforest::createUniformBlockGrid. + Specify blocks and cellsPerBlock. + + :param blocks: 3-tuple with total number of blocks in x,y,z direction. + When using this parameter you also have to pass cellsPerBlock. + :param cellsPerBlock: 3-tuple with total number of cells per block in x,y,z direction. + If this parameter is set, also blocks has to be set, but not cells + :param dx: Side length of a single cell. + :param oneBlockPerProcess: If True, each process gets one block. If False, all blocks are put to one process. + The second option makes only sense for debugging or testing. + :param periodic: Periodicity of the domain in x,y,z direction + :param keepGlobalBlockInformation: If true, each process keeps information about remote blocks (blocks that reside + on other processes). This information includes the process rank, the state, and + the axis-aligned bounding box of any block (local or remote). [false by default] + + \ No newline at end of file diff --git a/python/waLBerla_docs/modules/core.rst b/python/waLBerla_docs/modules/core.rst index c837f3a37..c1253ba29 100644 --- a/python/waLBerla_docs/modules/core.rst +++ b/python/waLBerla_docs/modules/core.rst @@ -27,11 +27,6 @@ Block Structure Creates a CellInterval from a given Python slice e.g. ``CellInterval.fromSlice( makeSlice[0:2,0:1,0:4] )`` - .. py:method:: __init__( minCell, maxCell ) - - Construct a cell interval given the minimum and maximum coordinate (cell). - The maxCell itself is included in the interval. - .. py:method:: __init__( xMin, yMin, zMin, xMax, yMax, zMax ) Constructs a cell interval using 6 integers corresponding to begin and end of the @@ -186,13 +181,10 @@ Block Structure -.. py:class:: StructuredBlockStorage - - StructuredBlockStorage represents a collection of blocks. It is an abstract class - and can not be created directly. A concrete implementation like the blockforest can - instantiate a StructuredBlockStorage. See blockforest.createUniformBlockGrid. - +.. py:class:: StructuredBlockForest + StructuredBlockForest represents a collection of blocks. It can be created using the createUniformBlockGrid method. + .. py:method:: getNumberOfLevels() .. py:method:: getDomain() @@ -201,8 +193,7 @@ Block Structure .. py:method:: mapToPeriodicDomain( x,y,z ) .. py:method:: mapToPeriodicDomain( point ) .. py:method:: mapToPeriodicDomain( cell, level=0 ) - - + .. py:method:: getBlock( x,y,z ) .. py:method:: containsGlobalBlockInformation( ) .. py:method:: blocksOverlappedByAABB( point, aabb ) diff --git a/python/waLBerla_docs/modules/field.rst b/python/waLBerla_docs/modules/field.rst index 81a9be47e..6357f6ea0 100644 --- a/python/waLBerla_docs/modules/field.rst +++ b/python/waLBerla_docs/modules/field.rst @@ -18,7 +18,7 @@ For details have a look at the C++ documentation: :doxylink:class:`walberla::fie Fields and numpy ================ -waLBerla *fields* can be easily converted to an :py:class:`numpy.ndarray` using Python's buffer protocol. This means +waLBerla *fields* can be easily converted to an :py:class:`numpy.ndarray` using the array functionality of pybind11. This means that a *numpy* array and a *field* can share the same memory such that fields can be manipulated using all the power of *numpy*:: @@ -27,16 +27,15 @@ power of *numpy*:: >>> field = waLBerla.field.createField( [3,3,3,1], float ) >>> field[0,0,0,0] 0.0 - >>> npArr = numpy.asarray( field.buffer() ) - >>> npArr = waLBerla.field.toArray( field ) # convenience function, same as above + >>> npArr = numpy.asarray( field ) + >>> npArr = waLBerla.field.toArray( field, True ) # convenience function, same as above (True includes ghostlayers) >>> npArr[:] = 42.0 >>> field[0,0,0,0] 42.0 A new *field* is created which is by default initialized with zero. Then a *numpy* array is created which shares the same data. After modifying the *numpy* array also the field -values have changed. To view the field including ghost layers additional parameters to -:py:meth:`Field.buffer` are required. +values have changed. A common source of error is to forget that some *numpy* functions create a copy of the data. The copy is of course not shared with the field anymore:: @@ -49,8 +48,8 @@ The copy is of course not shared with the field anymore:: When during the array manipulation a copy was created the result has to be copied back into the field again. Here the function :py:func:`numpy.copyto` is helpful::: - >>> numpy.copyto( numpy.asarray( field.buffer() ), npArr ) - >>> field = waLBerla.field.fromArray( npArr ) # convenience function, equivalent to above + >>> numpy.copyto( numpy.asarray( field ), npArr ) + >>> field = waLBerla.field.toArray( field ) # convenience function, equivalent to above >>> field[0,0,0] 5.0 @@ -67,14 +66,15 @@ Classes - Exported from C++ class :doxylink:class:`walberla::field::Field` - To modify or access a field class, the most convenient way is to create a *numpy.ndarray* view on it. - .. py:method:: buffer ( withGhostLayers=False ) + .. py:method:: __array__ - The returned object implements the Python Buffer Protocol and can be used for example to + The returned object implements pybind11::array and can be used for example to create a *numpy.ndarray* that shares the same data:: - numpy.asarray( field.buffer(withGhostLayers=True) ) - - The optional parameter specifies if the ghost layers are part of the buffer. + numpy.asarray( field ) + + With this function all ghostlayers are included in the view on the array. + If the fourth dimension is one (this means only one value per cell). The returned numpy array has only 3 dimensions. .. py:method:: swapDataPointers ( otherField ) @@ -122,63 +122,6 @@ Classes .. py:attribute:: nrOfGhostLayers The number of ghostlayers at each border of the field. - - -.. py:class:: FlagField - - Subclass of :py:class:`GhostLayerField` where the value type is an unsigned integer and the - size of the f coordinate is fixed to one element. FlagFields provide additional management function - for storing multiple booleans per cell (encoded in bits). - - - FlagFields are exported from C++ class :doxylink:class:`walberla::field::FlagField` - - .. py:method:: registerFlag( flagName, bitNr = None ) - - Reserves the next free bit (if bitNr is None ) or the specified bit using the provided flag name. - Returns an integer where the reserved bit is set to one, all other bits are set to zero. - - .. py:method:: flag( flagname ) - - Returns an integer where the specified flag is set to one, all other flags are zero. - - .. py:method:: flagName( flag ) - - Maps from integer where on bit is set to the name of the flag. - - .. py:attribute:: flags - - List with registered flag names. - - - -.. py:class:: FieldAdaptor - - A field adaptor is an object that emulates a GhostLayerField but does not store data itself. - Adaptors can only be created by C++ using :doxylink:class:`walberla::field::GhostLayerFieldAdaptor`. - - When accessing a cell of an adaptor, its value is computed on the fly based on one or multiple input fields. - A VelocityAdaptor, for example, computes the macroscopic velocity in a cell based on a field of particle distribution functions (PDFs). - Since adaptor do not hold data themselves they cannot be converted directly to numpy arrays ( see :py:meth:`copyToField` ). - Since this operation is expensive consider accessing only the required adaptor values using the getitem operator. - - .. py:method:: copyToField () - - Creates a field by computing the adaptor value for every cell (potentially expensive). - Returns this temporary field. Modifications of this field - do not affect the adaptor or the adaptor base field. - - - .. py:attribute:: size - - 4-tuple with sizes of (x,y,z,f) coordinates not counting ghost layers - - .. py:attribute:: sizeWithGhostLayer - - 4-tuple with sizes of (x,y,z,f) coordinates including ghost layers - - - Free Functions -------------- @@ -187,52 +130,34 @@ Free Functions Creates a new GhostLayerField - :param size: List of length 3 or 4 specifying x,y,z,f size of the field. - If list is of length 3 f-size is assumed to be 1 + :param size: List of length 4 specifying x,y,z,f size of the field. :param type: Type of the field elements. Valid types are the python types as well as some numpy types: - Integer types: int, numpy.int[8,16,32,64] - Unsigned types: numpy.uint[8,16,32,64] - Float types : float, numpy.float32, numpy.float64 + - Bool types : numpy.bool The type mapping is done via the C++ template trait ``walberla::python_coupling::isCppEqualToPythonType`` such that custom C++ types can be exported as well. :param ghostLayers: number of ghost layers of new field - :param layout: Either array-of-structures ``field.zyxf`` or structure-of-arrays ``field.fzyx`` - - - - + :param layout: Either array-of-structures ``field.zyxf`` or structure-of-arrays ``field.fzyx`` -.. py:function:: createFlagField( size, nrOfBits=32, ghostLayers=1 ) - - Creates a new FlagField - - :param size: list of length 3 with x,y,z size of field - :param nrOfBits: how many flags can be stored per cell. Allowed values are 8,16,32,64 - :param ghostLayers: number of ghost layers of new field - -.. note:: The ValueError "Cannot create field of this (type,f-size) combination" - means that in C++ this specific choice of type and f-size was not exported to Python. - In C++ these are template parameters, so a separate field class has to be instantiated for each - combination. - - - - -.. py:function:: addToStorage( blocks, name, type, fSize=1, ghostLayers=1, layout=field.fzyx, initValue=None) +.. py:function:: addToStorage( blocks, name, dtype, fSize=1, layout=field.fzyx, ghostLayers=1, initValue=0.0, alignment=0) Adds a GhostLayerField to the given blockStorage - :param blocks: the structured blockstorage where the field should be added to - :param name: name of block data, is used to retrieve the created field later on - :param initValue: initial value for all cells, if None the types are default initialized (for most types zero) - - The remaining parameter are the same as in :py:func:`createField` - - - + :param blocks: the structured blockstorage where the field should be added to + :param name: name of block data, is used to retrieve the created field later on + :param dtype: data type of the field + :param fSize: number of values per cell + :param layout: field.fzyx (SoA) or field.zyxf(AoS) + :param ghostLayers: number of ghost layers of the field + :param initValue: initial value for all cells, if None the types are default initialized (for most types zero) + :param alignment: alignment in bytes of the field vector + + .. py:function:: gather( blocks, blockDataName, slice, targetRank=0 ) Gathers part of the complete simulation domain (which is distributed to multiple processes) diff --git a/python/waLBerla_docs/modules/geometry.rst b/python/waLBerla_docs/modules/geometry.rst deleted file mode 100644 index 0a553af04..000000000 --- a/python/waLBerla_docs/modules/geometry.rst +++ /dev/null @@ -1,67 +0,0 @@ -*************** -Geometry module -*************** - - -.. py:class:: TriangleMesh - - Corresponds to C++ class :doxylink:class:`walberla::geometry::TriangleMesh` - - .. py:attribute:: numTriangles - - Number of triangles. - - .. py:attribute:: numVertices - - Number of vertices. - - .. py:attribute:: numVertexNormals - - Number of vertex normals. - - .. py:method:: getAABB() - - Returns the axis aligned bounding box of the mesh. - - .. py:method:: volume() - - Volume of the Mesh. - - .. py:method:: scale(factor) - - Scales the complete mesh by the given factor. - - .. py:method:: scaleXYZ( factors ) - - Scales the mesh by different factors in x,y,z direction. - - :param factors: tuple or list with 3 entries corresponding to x,y,z factors - - .. py:method:: exchangeAxes( xAxisId, yAxisId, zAxisId ) - - Permutes the coordinate order of each vertex. e.g. ``m.exchangeAxes(0,2,1)`` exchanges z and y axis. - - - .. py:method:: removeDuplicateVertices( tolerance = 1e-4) - - Merges vertices with a distance smaller than tolerance - - - .. py:method:: merge( other, offset=(0,0,0 ) ) - - Merges another mesh into the current mesh. During the merging all vertices of the - other mesh are shifted by the given offset. - - - .. py:method:: save( filename ) - - Saves the mesh to a file. The mesh format is deduced using the filename extension. - Supported formats are: obj,pov,off and vtp. - - .. py:staticmethod:: load( filename, broadcast=True ) - - Loads mesh from a file. The mesh format is deduced using the filename extension. - Supported formats are obj, pov and off. - - :param broadcast: If True the mesh is read on the root system only and broadcasted - to all other processes using MPI to reduce file system load. diff --git a/python/waLBerla_docs/modules/lbm.rst b/python/waLBerla_docs/modules/lbm.rst deleted file mode 100644 index 519462ef2..000000000 --- a/python/waLBerla_docs/modules/lbm.rst +++ /dev/null @@ -1,261 +0,0 @@ -********** -LBM module -********** - -.. note:: This module is deprecated and about to be replaced by the native Python module *lbmpy* - - -Creation Functions -================== - -.. py:function:: makeLatticeModel( stencil, collisionModel, forceModel, compressible, equilibriumAccuracyOrder=2 ) - - Creates a new lattice model. A lattice model encapsulates all information about the lattice Boltzmann method. - - :param stencil: a string describing the stencil in DxQy notation e.g. 'D2Q9', 'D3Q19', 'D3Q27' - :param collisionModel: an instance of a collision model - :param forceModel: an instance of a force model - :param compressible: choose either a compressible or incompressible LBM scheme - :param equilibriumAccuracyOrder: order of the equilibrium distribution. Valid values are 1 and 2. If not sure use 2 here. - - - .. note :: - The collision model and force model object are copied into the lattice model object. Changes to - the passed force or collision model do not affect the state of the lattice model. - Similarly after a sweep was created with a lattice model, the sweep itself is not changed when the lattice model - it was created with was changed. - - - -.. py:function:: addPdfFieldToStorage( blocks, name, latticeModel, initialVelocity=(0,0,0), initialDensity=1.0, ghostlayers=1, layout=field.zyxf, densityAdaptor="", velocityAdaptor="" ) - - Adds a PDFField to the provided blockstorage and optionally a density and velocity adaptor. - - :param blocks: blockstorage where the pdf field should be added to - :param name: block data id (string) of the new pdf field - :param latticeModel: see :py:meth:`makeLatticeModel` . The lattice model is copied into the pdf field. - Later changes to the provided object do not affect the pdf field. To change parameters of - the lattice model later, one has to iterate over all blocks, get the pdf field and retrieve - a lattice model reference from it. - :param initialVelocity: lattice velocity the field is initialized with - :param initialDensity: density the field is initialized with - :param ghostlayers: number of ghost layers, has to be at least one - :param layout: memory layout of the field, ( see documentation of field module ) - :param densityAdaptor: if a nonempty string is passed a :py:class:`FieldAdaptor` for the density is created with a - blockdataID of the given name - :param velocityAdaptor: if a nonempty string is passed a :py:class:`FieldAdaptor` for the velocity is created with a - blockdataID of the given name - - - - -.. py:function:: makeCellwiseSweep( blocks, pdfFieldID, flagFieldID="", flagList=[], velocityFieldID="" ) - - Creates a new LBM sweep. - - :param blocks: block storage where pdf field ( and if used, the flag field ) are stored - :param pdfFieldID: string identifier of the pdf field - :param flagFieldID: string identifier of the flag field. If empty string is passed, the LBM sweep is executed on all cells. - :param flagList: Only necessary when a flagFieldID was specified. Pass a list of flag identifiers here, - describing the flags where the sweep should be executed - :param velocityFieldID: optional velocity field ( field of fSize=3 and type=float) where the calculated velocity is written to. - - - - -.. py:class:: PdfField( field.GhostLayerField ) - - .. py:attribute:: latticeModel: - - - .. py:method:: setDensityAndVelocity( slice, velocity, density ) - - .. py:method:: setToEquilibrium( slice, velocity, density ) - - .. py:method:: getDensity( x,y,z ) - - .. py:method:: getDensitySI ( x,y,z, rho_SI ) - - .. py:method:: getMomentumDensity ( x,y,z ) - - .. py:method:: getEquilibriumMomentumDensity ( x,y,z ) - - .. py:method:: getVelocity ( x,y,z ) - - .. py:method:: getVelocitySI ( x,y,z. dx_SI, dt_SI ) - - .. py:method:: getEquilibriumVelocity ( x,y,z ) - - .. py:method:: getPressureTensor ( x,y,z ) - - - - -Boundary Handling -================= - -.. py:class:: BoundaryHandling - - .. py:method:: isEmpty( x,y,z ) - - .. py:method:: isNearBoundary( x,y,z ) - - .. py:method:: isBoundary( x,y,z ) - - .. py:method:: isDomain( x,y,z ) - - .. py:method:: setDomain( x, y, z | slice ) - - .. py:method:: forceDomain( x, y, z | slice ) - - .. py:method:: fillWithDomain( x, y, z | slice | nrOfGhostLayersToInclude ) - - .. py:method:: setBoundary( name, x, y, z | name, slice ) - - .. py:method:: forceBoundary( name, x, y, z | name, slice ) - - .. py:method:: removeDomain( x, y, z | slice | nrOfGhostLayersToInclude ) - - .. py:method:: removeBoundary( x, y, z | slice | nrOfGhostLayersToInclude ) - - .. py:method:: clear( x, y, z | slice | nrOfGhostLayersToInclude ) - - -Collision Models -================ - -.. py:class:: collisionModels.SRT - - Single Relaxation Time (BGK) lattice model - - .. py:method:: __init__( omega, level=0 ) - - - .. py:attribute:: omega: - - Relaxation parameter ( = 1/tau ) - - .. py:attribute:: viscosity: - - .. py:attribute:: level: - - .. py:method:: reset( omega, level=0 ) - - Sets a new relaxation parameter for the given level - - -.. py:class:: collisionModels.SRTField( SRT ) - - .. py:method:: __init__( omegaFieldID, level=0 ) - - :param omegaFieldID: this blockdata has to point to a floating point field of f-size=1 where for each cell - a different omega value is stored. - - - -.. py:class:: collisionModels.TRT - - .. py:method:: __init__( lambda_e, lambda_d, level=0 ) - - .. staticmethod:: constructWithMagicNumber( omega, magicNumber=3.0/16.0 , level=0 ) - - .. py:attribute:: lambda_e: - - .. py:attribute:: lambda_d: - - .. py:attribute:: viscosity: - - .. py:attribute:: level: - - .. py:method:: reset( lambda_e, lambda_d, level=0 ) - - .. py:method:: resetWithMagicNumber( omega, magicNumber=3.0/16.0 , level=0 ) - - -.. py:class:: collisionModels.D3Q19MRT - - .. py:method:: __init__( s1, s2, s4, s9, s10, s16, level=0 ) - - .. staticmethod:: constructTRTWithMagicNumber( omega, magicNumber=3.0/16.0 , level=0 ) - - .. staticmethod:: constructTRT( lambda_e, lambda_d, level=0 ) - - .. staticmethod:: constructPanWithMagicNumber( omega, magicNumber=3.0/16.0 , level=0 ) - - .. staticmethod:: constructPan( lambda_e, lambda_d, level=0 ) - - .. py:attribute:: relaxationRates: - - .. py:attribute:: viscosity: - - .. py:attribute:: level: - - - -Force Models -============ - -.. py:class:: forceModels.NoForce - -.. py:class:: forceModels.SimpleConstant - - .. py:method:: __init__( force, level=0 ) - -.. py:class:: forceModels.EDMField - - .. py:method:: __init__( forceFieldID ) - -.. py:class:: forceModels.LuoConstant - - .. py:method:: __init__( force, level=0 ) - -.. py:class:: forceModels.LuoField - - .. py:method:: __init__( forceFieldID ) - -.. py:class:: forceModels.GuoConstant - - .. py:method:: __init__( force, level=0 ) - -.. py:class:: forceModels.Correction - - .. py:method:: __init__( previousMomentumDensityFieldID ) - - -Lattice Models -============== - -.. py:class:: LatticeModel - - Lattice models are created with the function :func:`makeLatticeModel` and encapsulate information about - stencil, collision operator and force model. - This information can be accessed through the following read-only attributes. - - .. py:attribute:: collisionModel: - - a *copy* of the collision model - - .. py:attribute:: forceModel: - - a *copy* of the force model - - .. py:attribute:: compressible: - - boolean signaling a compressible model - - .. py:attribute:: stencilName: - - a string describing the stencil in *DxQy* notation - - .. py:attribute:: communicationStencilName: - - name of stencil that should be used for communication. In most cases this is the same as stencilName - - .. py:attribute:: directions: - - a list of tuples containing the directions of the stencil. e.g. (0,0,0) for center, (1,0,0) for east etc. - For a DxQy stencil the list as y entries, the tuples are of length x. - - - - diff --git a/python/waLBerla_tests/test_blockforest.py b/python/waLBerla_tests/test_blockforest.py index 59e0057b2..09a158151 100644 --- a/python/waLBerla_tests/test_blockforest.py +++ b/python/waLBerla_tests/test_blockforest.py @@ -1,36 +1,79 @@ import unittest -from waLBerla import field, createUniformBlockGrid +import numpy as np +from waLBerla import field, createUniformBlockGrid, AABB class BlockforestModuleTest(unittest.TestCase): def testMemoryManagement1(self): """Testing correct reference counting of block data""" - blocks = createUniformBlockGrid(cells=(2, 2, 2)) - field.addToStorage(blocks, "TestField", float) - f = blocks[0]['TestField'] - stridesBefore = f.strides + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) + field.addToStorage(blocks, "TestField", np.float64) + f = blocks[0]["TestField"] + strides_before = f.strides del blocks # create another block structure - this has triggered segfault # when previous blockstructure was already freed - blocks = createUniformBlockGrid(cells=(2, 2, 2)) # noqa: F841 + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) # noqa: F841 # The first block structure must exist here, since we hold a reference to block data # if it would have been deleted already f.strides should lead to segfault or invalid values - self.assertEqual(stridesBefore, f.strides) + self.assertEqual(strides_before, f.strides) def testMemoryManagement2(self): """Testing correct reference counting of block data Holding only a numpy array pointing to a waLBerla field should still hold the blockstructure alive""" - blocks = createUniformBlockGrid(cells=(2, 2, 2)) - field.addToStorage(blocks, "TestField", float) - npf = field.toArray(blocks[0]['TestField']) - npf[:, :, :, :] = 42.0 + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) + field.addToStorage(blocks, "TestField", np.float64) + npf = field.toArray(blocks[0]["TestField"]) + npf[:, :, :] = 42.0 del blocks # create another block structure - this has triggered segfault # when previous blockstructure was already freed - blocks = createUniformBlockGrid(cells=(2, 2, 2)) # noqa: F841 - self.assertEqual(npf[0, 0, 0, 0], 42.0) + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) # noqa: F841 + self.assertEqual(npf[0, 0, 0], 42.0) + + def testMemoryManagement3(self): + """Same as testMemoryManagement2, but with iterators""" + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) + field.addToStorage(blocks, "TestField", np.float64) + for block in blocks: + for name in block.fieldNames: + if name == "TestField": + f = block[name] + npf = field.toArray(f) + npf[:, :, :] = 42.0 + del blocks, block, name, f + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) # noqa: F841 + self.assertEqual(npf[0, 0, 0], 42.0) + + def testExceptions(self): + """Check that the right exceptions are thrown when nonexistent or non-convertible fields are accessed""" + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) + with self.assertRaises(ValueError) as cm: + blocks[0]["cell bounding box"] + self.assertEqual(str(cm.exception), "This blockdata is not accessible from Python") + with self.assertRaises(IndexError) as cm: + blocks[0]["nonexistent"] + self.assertEqual(str(cm.exception), "No blockdata with the given name found") + + def testGeneralFunctionality(self): + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(2, 2, 2)) + self.assertEqual(blocks.getNumberOfLevels(), 1) + + aabb = blocks.getDomain + aabb2 = AABB(1.0, 1.0, 1.0, 1.2, 1.2, 1.2) + + self.assertEqual(aabb.min, (0.0, 0.0, 0.0)) + self.assertEqual(aabb.max, (2.0, 2.0, 2.0)) + self.assertEqual(aabb.size, (2.0, 2.0, 2.0)) + self.assertEqual(aabb.empty, False) + self.assertEqual(aabb.volume, 8.0) + self.assertEqual(aabb.center, (1.0, 1.0, 1.0)) + self.assertEqual(aabb.contains(aabb2), True) + self.assertEqual(aabb2.contains(aabb), False) + self.assertEqual(aabb2.contains((1.2, 1.2, 1.2)), False) + self.assertEqual(aabb2.contains((1.1, 1.1, 1.1)), True) if __name__ == '__main__': diff --git a/python/waLBerla_tests/test_core.py b/python/waLBerla_tests/test_core.py index 7d59da398..7a286876e 100644 --- a/python/waLBerla_tests/test_core.py +++ b/python/waLBerla_tests/test_core.py @@ -6,7 +6,7 @@ class CoreTest(unittest.TestCase): def test_CellInterval(self): ci1 = wlb.CellInterval(0, 0, 0, 5, 5, 5) - ci2 = wlb.CellInterval([0] * 3, [5] * 3) + ci2 = wlb.CellInterval(0, 0, 0, 5, 5, 5) self.assertEqual(ci1, ci2, "Equality comparison of CellIntervals failed.") self.assertFalse(ci1 != ci2, "Inequality check for CellIntervals wrong ") @@ -23,7 +23,7 @@ class CoreTest(unittest.TestCase): def test_AABB(self): aabb1 = wlb.AABB(0, 0, 0, 5, 5, 5) - aabb2 = wlb.AABB([0] * 3, [5] * 3) + aabb2 = wlb.AABB(0, 0, 0, 5, 5, 5) self.assertEqual(aabb1, aabb2) diff --git a/python/waLBerla_tests/test_cuda_comm.py b/python/waLBerla_tests/test_cuda_comm.py deleted file mode 100644 index 3ef61ee30..000000000 --- a/python/waLBerla_tests/test_cuda_comm.py +++ /dev/null @@ -1,25 +0,0 @@ -from waLBerla import field, createUniformBlockGrid, createUniformBufferedScheme, cuda -import numpy as np -import pycuda.autoinit # noqa: F401 -import pycuda.gpuarray as gpuArr -# from pycuda import * -from pystencils.field import createNumpyArrayWithLayout, getLayoutOfArray - -blocks = createUniformBlockGrid(cells=(1, 1, 1), periodic=(1, 1, 1)) -cuda.addGpuFieldToStorage(blocks, "gpuField", float, fSize=1, ghostLayers=1, layout=field.fzyx, usePitchedMem=False) - -gpuArr = cuda.toGpuArray(blocks[0]['gpuField']) # noqa: F811 - -testField = createNumpyArrayWithLayout(gpuArr.shape, getLayoutOfArray(gpuArr)) -testField[...] = 0 -testField[1, 1, 1, 0] = 1 -gpuArr.set(testField) - -scheme = createUniformBufferedScheme(blocks, "D3Q27") -scheme.addDataToCommunicate(cuda.createPackInfo(blocks, "gpuField")) - -scheme() - -gpuArr = cuda.toGpuArray(blocks[0]['gpuField']) - -assert (np.allclose(np.ones([3, 3, 3, 1]), gpuArr.get())) diff --git a/python/waLBerla_tests/test_field.py b/python/waLBerla_tests/test_field.py index 9f984b3e0..85957c51b 100644 --- a/python/waLBerla_tests/test_field.py +++ b/python/waLBerla_tests/test_field.py @@ -1,57 +1,50 @@ import unittest +import numpy as np +import waLBerla as wlb from waLBerla import field, createUniformBlockGrid class FieldModuleTest(unittest.TestCase): def testFieldAsBlockData(self): - blocks = createUniformBlockGrid(cells=(3, 2, 2), periodic=(1, 0, 0)) - field.addToStorage(blocks, 'myField', float, fSize=3, ghostLayers=0, initValue=0.0) - myField = blocks[0]['myField'] - self.assertEqual(myField[0, 0, 0, 0], 0) - myField[0, 0, 0, 0] = 42.0 - self.assertEqual(myField[0, 0, 0, 0], 42.0) + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(3, 2, 2), periodic=(True, False, False)) + field.addToStorage(blocks, 'myField', np.float64, fSize=3, ghostLayers=0, initValue=0.0) + my_field = wlb.field.toArray(blocks[0]['myField']) + self.assertEqual(my_field[0, 0, 0, 0], 0) + my_field[0, 0, 0, 0] = 42.0 + self.assertEqual(my_field[0, 0, 0, 0], 42.0) - self.assertRaises(IndexError, myField.__getitem__, (3, 0, 0)) + self.assertRaises(IndexError, my_field.__getitem__, (3, 0, 0)) def testNumpyConversionWithoutGhostLayers(self): - f1 = field.createField([1, 2, 3, 4], float, 2, field.zyxf) - f2 = field.createField([1, 2, 3, 5], float, 4, field.zyxf) - f1np = field.toArray(f1) - f2np = field.toArray(f2) - self.assertEqual(f1np[0, 0, 0, 0], 0) + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=(1, 2, 3), periodic=(True, False, False)) + field.addToStorage(blocks, 'f1', np.float64, fSize=4, ghostLayers=0, initValue=2.0) + field.addToStorage(blocks, 'f2', np.float64, fSize=5, ghostLayers=0, initValue=2.0) + + f1np = field.toArray(blocks[0]['f1']) + f2np = field.toArray(blocks[0]['f2']) + self.assertEqual(f1np[0, 0, 0, 0], 2.0) self.assertEqual(f1np.shape, (1, 2, 3, 4)) self.assertEqual(f2np.shape, (1, 2, 3, 5)) - f1np[0, 0, 0, 0] = 1 - f2np[0, 0, 0, 0] = 2 - self.assertEqual(f1[0, 0, 0, 0], 1) - self.assertEqual(f2[0, 0, 0, 0], 2) - - def testNumpyConversionWithGhostLayers(self): - f = field.createField([1, 2, 3, 1], float, 2, field.zyxf) - fnp = field.toArray(f, withGhostLayers=True) - - self.assertEqual(fnp[0, 0, 0, 0], 0) - self.assertEqual(fnp.shape, (1 + 4, 2 + 4, 3 + 4, 1)) - fnp[0, 0, 0, 0] = 42 - self.assertEqual(f[-2, -2, -2, 0], 42) - def testGhostLayerExtraction(self): - size = [10, 5, 4] + size = (10, 5, 4) gl = 3 - f = field.createField(size, float, ghostLayers=gl) + blocks = createUniformBlockGrid(blocks=(1, 1, 1), cellsPerBlock=size, periodic=(True, False, False)) + field.addToStorage(blocks, 'f', np.float64, fSize=3, ghostLayers=gl, initValue=0.0) + + f = blocks[0]['f'] - view1 = field.toArray(f, withGhostLayers=True) + view1 = field.toArray(f, with_ghost_layers=True) self.assertEqual(view1[:, :, :, 0].shape, tuple([s + 2 * gl for s in size])) - view2 = field.toArray(f, withGhostLayers=False) + view2 = field.toArray(f, with_ghost_layers=False) self.assertEqual(view2[:, :, :, 0].shape, tuple(size)) - view3 = field.toArray(f, withGhostLayers=2) + view3 = field.toArray(f, with_ghost_layers=2) self.assertEqual(view3[:, :, :, 0].shape, tuple([s + 2 * 2 for s in size])) - view4 = field.toArray(f, withGhostLayers=[2, False, True]) + view4 = field.toArray(f, with_ghost_layers=[2, False, True]) self.assertEqual(view4[:, :, :, 0].shape, tuple([size[0] + 2 * 2, size[1] + 2 * 0, size[2] + 2 * gl])) diff --git a/python/waLBerla_tests/test_simpleLBM.py b/python/waLBerla_tests/test_simpleLBM.py deleted file mode 100644 index defe348eb..000000000 --- a/python/waLBerla_tests/test_simpleLBM.py +++ /dev/null @@ -1,198 +0,0 @@ -from waLBerla import makeSlice, field, mpi, lbm, createUniformBlockGrid, createUniformBufferedScheme -from waLBerla.geometry_setup import setBoundaryFromBlackAndWhiteImage, setFieldUsingFlagMask -import itertools - -import os -import numpy as np -import scipy - -imageFile = os.path.join(os.path.dirname(__file__), 'wing.png') - - -def setBoundariesChannel(blocks, boundaryHandlingID): - for block in blocks: - b = block[boundaryHandlingID] - if block.atDomainMinBorder[1]: - b.forceBoundary('NoSlip', makeSlice[:, 0, :, 'g']) - if block.atDomainMaxBorder[1]: - b.forceBoundary('NoSlip', makeSlice[:, -1, :, 'g']) - b.fillWithDomain() - - -class ForceCalculationMasks: - @staticmethod - def addToBlock(block, blockStorage): - pdfFieldArr = field.toArray(block['pdfs']) - flagFieldArr = field.toArray(block['flags'])[:, :, :, 0] - directions = block['pdfs'].latticeModel.directions - maskArr = np.zeros(pdfFieldArr.shape, dtype=bool) - pdfDirectionArr = np.zeros(list(pdfFieldArr.shape) + [3]) - - nearBoundaryFlag = block['flags'].flag("fluid") - noSlipFlag = block['flags'].flag("NoSlip") - - innerPartOfDomain = itertools.product(range(2, maskArr.shape[0] - 2), - range(2, maskArr.shape[1] - 2), - range(maskArr.shape[2])) - - for x, y, z in innerPartOfDomain: - if flagFieldArr[x, y, z] & nearBoundaryFlag: - for dirIdx, dir in enumerate(directions): - nx, ny, nz = x + dir[0], y + dir[1], z + dir[2] - if flagFieldArr[nx, ny, nz] & noSlipFlag: - maskArr[x, y, z, dirIdx] = True - pdfDirectionArr[x, y, z, :] = dir - return ForceCalculationMasks(maskArr, pdfDirectionArr) - - def __init__(self, maskArr, pdfDirectionArr): - self._maskArr = maskArr - self._pdfDirectionArr = pdfDirectionArr - - def calculateForceOnBoundary(self, pdfField): - force = np.array([0.0] * 3) - pdfFieldArr = field.toArray(pdfField) - for i in range(3): - fArr = pdfFieldArr[self._maskArr] * self._pdfDirectionArr[self._maskArr, i] - force[i] += np.sum(fArr) - return force - - -def calculateForceOnBoundary(blocks): - force = np.array([0.0] * 3) - for block in blocks: - force += block['ForceCalculation'].calculateForceOnBoundary(block['pdfs']) - return np.array(mpi.reduceReal(force, mpi.SUM)) - - -def makeNacaAirfoilImage(domainSize, thicknessInPercent=30, angle=0): - def nacaAirfoil(x, thicknessInPercent, chordLength): - xOverC = x / chordLength - y_t = 0 - coeffs = [0.2969, -0.1260, - 0.3516, 0.2843, -0.1015] - for coeff, exponent in zip(coeffs, [0.5, 1, 2, 3, 4]): - y_t += coeff * xOverC ** exponent - y_t *= 5 * thicknessInPercent / 100 * chordLength - return y_t - - domain = np.zeros(domainSize) - it = np.nditer(domain, flags=['multi_index'], op_flags=['readwrite']) - while not it.finished: - x, y = it.multi_index - y -= domain.shape[1] / 2 - if abs(y) < nacaAirfoil(x, thicknessInPercent, domain.shape[0]): - it[0] = 1 - it.iternext() - domain = np.rot90(domain, 1) - domain = scipy.ndimage.interpolation.rotate(domain, angle=angle) - - domain[domain > 0.5] = 1 - domain[domain <= 0.5] = 0 - domain = domain.astype(np.int32) - return domain - - -img = makeNacaAirfoilImage([300, 300], 30, angle=-30) - -omega = 1.9 -blocks = createUniformBlockGrid(cells=(500, 200, 1), periodic=(1, 0, 1)) - -collisionModel = lbm.collisionModels.SRT(omega) -forceModel = lbm.forceModels.SimpleConstant((1e-5, 0, 0)) -latticeModel = lbm.makeLatticeModel("D2Q9", collisionModel, forceModel) -lbm.addPdfFieldToStorage(blocks, "pdfs", latticeModel, velocityAdaptor="vel", densityAdaptor="rho", initialDensity=1.0) -field.addFlagFieldToStorage(blocks, 'flags') -lbm.addBoundaryHandlingToStorage(blocks, 'boundary', 'pdfs', 'flags') - -# setBoundaryFromArray( blocks, 'boundary', makeSlice[0.4:0.6, 0.4:0.55 ,0.5], img, { 1: 'NoSlip' } ) -setBoundaryFromBlackAndWhiteImage(blocks, "boundary", makeSlice[0.25:0.75, 0.3:0.6, 0.5], imageFile, "NoSlip") -setBoundariesChannel(blocks, 'boundary') - -blocks.addBlockData('ForceCalculation', ForceCalculationMasks.addToBlock) - -sweep = lbm.makeCellwiseSweep(blocks, "pdfs", flagFieldID='flags', flagList=['fluid']) - -scheme = createUniformBufferedScheme(blocks, 'D3Q19') -scheme.addDataToCommunicate(field.createPackInfo(blocks, 'pdfs')) - - -def timestep(): - scheme() - for block in blocks: - block['boundary']() - for block in blocks: - sweep.streamCollide(block) - return calculateForceOnBoundary(blocks) - - -def run(timesteps): - for t in range(timesteps): - scheme() - for block in blocks: - block['boundary']() - for block in blocks: - sweep.streamCollide(block) - - -def makeAnimation(blocks, interval=30, frames=180): - import matplotlib.pyplot as plt - import matplotlib.animation as animation - - plt.style.use('ggplot') - NR_OF_TIMESTEPS_SHOWN = 600 - lifts = [] - - fig = plt.gcf() - f = field.gather(blocks, 'rho', makeSlice[:, :, 0.5]) - im = None - - ymax = [0.05] - if f: - npField = field.toArray(f).squeeze() - npField = np.rot90(npField, 1) - - plt.subplot(2, 1, 1) - plt.title("Lattice Density") - im = plt.imshow(npField) - plt.colorbar() - - plt.subplot(2, 1, 2) - plt.title("Lift") - plt.ylim(0, ymax[0]) - plt.xlim(0, NR_OF_TIMESTEPS_SHOWN) - liftPlot, = plt.plot(lifts) - - def updatefig(*args): - force = timestep() - f = field.gather(blocks, 'rho', makeSlice[:, :, 0.5]) - if f: - npField = field.toArray(f).squeeze() - npField = np.rot90(npField, 1) - im.set_array(npField) - im.autoscale() - if lifts and max(lifts) * 1.2 > ymax[0]: - ymax[0] = max(lifts) * 1.2 - liftPlot.axes.set_ylim(0, ymax[0]) - - lifts.append(force[1]) - nrOfSamples = len(lifts) - xMin = max(0, nrOfSamples - NR_OF_TIMESTEPS_SHOWN) - liftPlot.axes.set_xlim(xMin, xMin + NR_OF_TIMESTEPS_SHOWN) - liftPlot.set_data(np.arange(nrOfSamples), lifts) - return im, liftPlot - - return animation.FuncAnimation(fig, updatefig, interval=interval, frames=frames, blit=False, repeat=False) - - -showPlots = False - -if showPlots: - import waLBerla.plot as wplt - - setFieldUsingFlagMask(blocks, 'pdfs', np.NaN, 'flags', ['NoSlip']) - run(1) - setFieldUsingFlagMask(blocks, 'pdfs', np.NaN, 'flags', ['NoSlip']) - - ani = makeAnimation(blocks, frames=6000, ) - wplt.show() -else: - run(10) diff --git a/python/waLBerla_tests/tools/test_lbm_unitconversion.py b/python/waLBerla_tests/tools/test_lbm_unitconversion.py index 72d87aa35..7631b5776 100644 --- a/python/waLBerla_tests/tools/test_lbm_unitconversion.py +++ b/python/waLBerla_tests/tools/test_lbm_unitconversion.py @@ -5,9 +5,9 @@ class UnitConversionTest(unittest.TestCase): def testExtractLatticeFactors(self): try: - import pint # noqa: F401 + import pint, sympy # noqa: F401 except ImportError: - print("Skipping unit conversion test since pint module not available") + print("Skipping unit conversion test since pint or sympy module not available") return from waLBerla.tools.lbm_unitconversion import extractLatticeFactors, computeLatticeFactors @@ -28,9 +28,9 @@ class UnitConversionTest(unittest.TestCase): def testUnitConverter(self): try: - import pint # noqa: F401 + import pint, sympy # noqa: F401 except ImportError: - print("Skipping unit conversion test since pint module not available") + print("Skipping unit conversion test since pint or sympy module not available") return from waLBerla.tools.lbm_unitconversion import PintUnitConverter diff --git a/python/waLBerla_tests/wing.png b/python/waLBerla_tests/wing.png deleted file mode 100644 index f51549c70bf0f33840c03536577b4e8591eec70a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1146 zcmeAS@N?(olHy`uVBq!ia0y~yU~&PnH*+un$>&{DT7eX2fk$L90|Va?5N4dJ%_j{M zWH0gbb!C6R%)zUrz!EuU9RmZ4q^FBxNX4zUcLM!hI|#T0s-60`-R6%n>(Ob7>(&*E zHHcL2%4eH*?^bm^qr;|lcZP-;j1Z)d#sEhhk_>Q^#Kr(aaJ4`NTrH3RR|{l7)c$|> zQt-p6sMnL`Z~p$xpmG1*aIszv)uKE`!^tc3_$)glyOvfq%utLCV(*w)qFCB6W2Lwi zds17~!UBafDK;+lq-BX3GC*eAp2isq6HiEY%<O60);J?m0?JfOGh+M122`bxqX1;y z0y0w+a)7EB54FuWDUkuzu)(qLObSm6uVJu}!wmsrNoFuJ!1P5I(6kMTX<lqAq>|K< z8=%ZmpgIAwBRao*G$dC@r5yJ;$q=e+0W^Ju)J>2PS5NfKoW!`wt?bMco&eKL$;(n2 zR!x*OR%H(9eKSK-QXvzf<#D-xKf{CL;pw5p#>KkKF;@h@N(@0pT&kG7e<q{M@3ysB zd1>oX8@i=r!NL|t)=M7T^`-SwQ~FY%%rcJJuK1@Ovt~&0tl9Q(yV!a;L*{JnhjVz3 z@T^g9ElxXfrqONJ27x)MhQh{cq~EZo`<ywDmUwMf!G|L}I)ZIE{L2e-&V1p~Zr<km zsHRKu1|QE&Exxx~X1tUvW8C`ssG*#wvF+gxm-nR4d1YyM*|^8*Dfe+pc_E-+Lh@&e zpTbGY(r2WsQD`<$OsiTJmmoN^j}xejm-+RH4L%(+mvBl%cpXdkIHTfw_&}VIuXEC} zEGD*ZlOE0zHue-Y*f6W-_JkRpvl<%TrZ8Mz!mpqCiuEb?sxp=9SN!#I+*q@=2S1#% zuI;=|&N3#h@ZhT-)@=(_&0%Ax`WAHFV*M+739i{Cjt7HQ{otJwdu@VwzNF5go7E0y z++5GyW|_Ub*tH|l;q4yf4RLEk44DJAKC~4!X7B{!NvqAC*qe$NH+)*c%h5Q4aYa<k z`6E0ImmW=4OJkTMcHbDJ`p1$^$%Z9Izx$kFP|@9&p2Vh*`4OZ!wEO+c8H^stT4X$K z*{)VhW03K_#d{b?&slkE?I#fJbE}q12}t|j@>K%TN7Q}KFzissoWJ(u;cmtUwbl9m zX9+fJ+`fgM?dVyah9A+hW=k@BymN*@z!-{-@G!uUAu|IEIh<jDBe+^11FjaxfT;yC i;A(*kB9t#UX<q!}z*m)5M!SIJ27{-opUXO@geCym?x`RE diff --git a/src/blockforest/CMakeLists.txt b/src/blockforest/CMakeLists.txt index 554388c3f..84027d327 100644 --- a/src/blockforest/CMakeLists.txt +++ b/src/blockforest/CMakeLists.txt @@ -5,4 +5,4 @@ mark_as_advanced( WALBERLA_BLOCKFOREST_PRIMITIVE_BLOCKID ) configure_file ( CMakeDefs.in.h CMakeDefs.h ) -waLBerla_add_module( DEPENDS communication core domain_decomposition python_coupling stencil ) +waLBerla_add_module( DEPENDS communication core domain_decomposition stencil ) diff --git a/src/blockforest/communication/UniformBufferedScheme.h b/src/blockforest/communication/UniformBufferedScheme.h index b9c07bbec..6ee1be18c 100644 --- a/src/blockforest/communication/UniformBufferedScheme.h +++ b/src/blockforest/communication/UniformBufferedScheme.h @@ -32,7 +32,6 @@ #include "core/Set.h" #include "core/debug/CheckFunctions.h" #include "core/debug/Debug.h" -#include "core/WeakPtrWrapper.h" #include "core/mpi/MPIManager.h" #include "core/mpi/OpenMPBufferSystem.h" #include "core/selectable/IsSetSelected.h" @@ -96,7 +95,7 @@ public: /*! \name Construction & Destruction */ //@{ - explicit UniformBufferedScheme( weak_ptr_wrapper<StructuredBlockForest> bf, + explicit UniformBufferedScheme( weak_ptr<StructuredBlockForest> bf, const int tag = 778 ) // waLBerla = 119+97+76+66+101+114+108+97 : blockForest_( bf ), localMode_( START ), @@ -111,7 +110,7 @@ public: forestModificationStamp_ = forest->getBlockForest().getModificationStamp(); } - UniformBufferedScheme( weak_ptr_wrapper<StructuredBlockForest> bf, + UniformBufferedScheme( weak_ptr<StructuredBlockForest> bf, const Set<SUID> & requiredBlockSelectors, const Set<SUID> & incompatibleBlockSelectors, const int tag = 778 ) // waLBerla = 119+97+76+66+101+114+108+97 @@ -177,7 +176,7 @@ protected: - weak_ptr_wrapper<StructuredBlockForest> blockForest_; + weak_ptr<StructuredBlockForest> blockForest_; uint_t forestModificationStamp_; std::vector< PackInfo > packInfos_; diff --git a/src/blockforest/communication/UniformDirectScheme.h b/src/blockforest/communication/UniformDirectScheme.h index bf3a73344..7dfa2d781 100644 --- a/src/blockforest/communication/UniformDirectScheme.h +++ b/src/blockforest/communication/UniformDirectScheme.h @@ -24,7 +24,6 @@ #include "blockforest/StructuredBlockForest.h" #include "core/Set.h" -#include "core/WeakPtrWrapper.h" #include "core/mpi/Datatype.h" #include "core/mpi/MPIManager.h" #include "core/mpi/MPIWrapper.h" @@ -56,7 +55,7 @@ public: //**Construction & Destruction*************************************************************************************** /*! \name Construction & Destruction */ //@{ - explicit UniformDirectScheme( const weak_ptr_wrapper<StructuredBlockForest> & bf, + explicit UniformDirectScheme( const weak_ptr<StructuredBlockForest> & bf, const shared_ptr<UniformMPIDatatypeInfo> & dataInfo = shared_ptr<UniformMPIDatatypeInfo>(), const int tag = 778 ) // waLBerla = 119+97+76+66+101+114+108+97 : blockForest_( bf ), @@ -70,7 +69,7 @@ public: dataInfos_.push_back( dataInfo ); } - UniformDirectScheme( const weak_ptr_wrapper<StructuredBlockForest> & bf, + UniformDirectScheme( const weak_ptr<StructuredBlockForest> & bf, const Set<SUID> & requiredBlockSelectors, const Set<SUID> & incompatibleBlockSelectors, const shared_ptr<UniformMPIDatatypeInfo> & dataInfo = shared_ptr<UniformMPIDatatypeInfo>(), @@ -140,7 +139,7 @@ protected: } }; - weak_ptr_wrapper<StructuredBlockForest> blockForest_; + weak_ptr<StructuredBlockForest> blockForest_; bool setupRequired_; //< this is set in the beginning or when new communication item was added bool communicationRunning_; //< this is true between startCommunication() and wait() diff --git a/src/blockforest/python/CommunicationExport.impl.h b/src/blockforest/python/CommunicationExport.impl.h deleted file mode 100644 index 09041944f..000000000 --- a/src/blockforest/python/CommunicationExport.impl.h +++ /dev/null @@ -1,235 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file CommunicationExport.impl.h -//! \ingroup blockforest -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - - -#include "blockforest/communication/UniformBufferedScheme.h" -#include "blockforest/communication/UniformDirectScheme.h" -#include "python_coupling/helper/MplHelpers.h" - -namespace walberla { -namespace blockforest { - -namespace internal -{ - //=================================================================================================================== - // - // UniformBufferedScheme - // - //=================================================================================================================== - - /// for details see documentation in core/WeakPtrWrapper.h - /// the purpose of this class could also be solved by adding return_internal_reference to "createUniformDirectScheme" - /// however this is not easily possible since it returns not a reference but an boost::python::object - template<typename Stencil> - class UniformBufferedSchemeWrapper : public blockforest::communication::UniformBufferedScheme<Stencil> - { - public: - UniformBufferedSchemeWrapper( const shared_ptr<StructuredBlockForest> & bf, const int tag ) - : blockforest::communication::UniformBufferedScheme<Stencil>( bf, tag), - blockforest_( bf) - {} - private: - shared_ptr< StructuredBlockForest > blockforest_; - }; - - - struct UniformBufferedSchemeExporter - { - template<typename Stencil> - void operator() ( python_coupling::NonCopyableWrap<Stencil> ) - { - using namespace boost::python; - typedef UniformBufferedSchemeWrapper<Stencil> UBS; - - class_< UBS, shared_ptr<UBS>, boost::noncopyable >( "UniformBufferedScheme", no_init ) - .def( "__call__", &UBS::operator() ) - .def( "communicate", &UBS::communicate ) - .def( "startCommunication", &UBS::startCommunication ) - .def( "wait", &UBS::wait ) - .def( "addPackInfo", &UBS::addPackInfo ) - .def( "addDataToCommunicate", &UBS::addDataToCommunicate ) - .def( "localMode", &UBS::localMode ) - .def( "setLocalMode", &UBS::setLocalMode ) - ; - } - }; - - class UniformBufferedSchemeCreator - { - public: - UniformBufferedSchemeCreator( const shared_ptr<StructuredBlockForest> & bf, - const std::string & stencilName, - const int tag ) - : blockforest_( bf), stencilName_( stencilName ), tag_( tag ) - {} - - template<typename Stencil> - void operator() ( python_coupling::NonCopyableWrap<Stencil> ) - { - - using namespace boost::python; - if ( std::string(Stencil::NAME) == stencilName_ ) { - result_ = object ( make_shared< UniformBufferedSchemeWrapper<Stencil> > ( blockforest_, tag_ ) ); - } - } - - boost::python::object getResult() { return result_; } - private: - shared_ptr<StructuredBlockForest> blockforest_; - std::string stencilName_; - const int tag_; - boost::python::object result_; - }; - - - template<typename Stencils> - boost::python::object createUniformBufferedScheme( const shared_ptr<StructuredBlockForest> & bf, - const std::string & stencil, - const int tag ) - { - UniformBufferedSchemeCreator creator( bf, stencil, tag ); - python_coupling::for_each_noncopyable_type< Stencils > ( std::ref(creator) ); - - if ( creator.getResult() == boost::python::object() ) - { - PyErr_SetString( PyExc_RuntimeError, "Unknown stencil."); - throw boost::python::error_already_set(); - } - return creator.getResult(); - } - - //=================================================================================================================== - // - // UniformDirectScheme - // - //=================================================================================================================== - - template<typename Stencil> - class UniformDirectSchemeWrapper : public blockforest::communication::UniformDirectScheme<Stencil> - { - public: - UniformDirectSchemeWrapper( const shared_ptr<StructuredBlockForest> & bf, const int tag ) - : blockforest::communication::UniformDirectScheme<Stencil>( bf, shared_ptr<walberla::communication::UniformMPIDatatypeInfo>(), tag), - blockforest_( bf) - {} - private: - shared_ptr< StructuredBlockForest > blockforest_; - }; - - struct UniformDirectSchemeExporter - { - template<typename Stencil> - void operator() ( python_coupling::NonCopyableWrap<Stencil> ) - { - using namespace boost::python; - typedef UniformDirectSchemeWrapper<Stencil> UDS; - - class_< UDS, shared_ptr<UDS>, boost::noncopyable >( "UniformDirectScheme", no_init ) - .def( "__call__", &UDS::operator() ) - .def( "communicate", &UDS::communicate ) - .def( "startCommunication", &UDS::startCommunication ) - .def( "wait", &UDS::wait ) - .def( "addDataToCommunicate", &UDS::addDataToCommunicate ) - ; - } - }; - - class UniformDirectSchemeCreator - { - public: - UniformDirectSchemeCreator( const shared_ptr<StructuredBlockForest> & bf, - const std::string & stencilName, - const int tag ) - : blockforest_( bf), stencilName_( stencilName ), tag_( tag ) - {} - - template<typename Stencil> - void operator() ( python_coupling::NonCopyableWrap<Stencil> ) - { - - using namespace boost::python; - if ( std::string(Stencil::NAME) == stencilName_ ) { - result_ = object ( make_shared< UniformDirectSchemeWrapper<Stencil> > ( blockforest_, tag_ ) ); - } - } - - boost::python::object getResult() { return result_; } - private: - shared_ptr<StructuredBlockForest> blockforest_; - std::string stencilName_; - const int tag_; - boost::python::object result_; - }; - - - template<typename Stencils> - boost::python::object createUniformDirectScheme( const shared_ptr<StructuredBlockForest> & bf, - const std::string & stencil, const int tag ) - { - UniformDirectSchemeCreator creator( bf, stencil, tag ); - python_coupling::for_each_noncopyable_type< Stencils > ( std::ref(creator) ); - - if ( creator.getResult() == boost::python::object() ) - { - PyErr_SetString( PyExc_RuntimeError, "Unknown stencil."); - throw boost::python::error_already_set(); - } - return creator.getResult(); - } - -} - - -template<typename Stencils> -void exportUniformBufferedScheme() -{ - using namespace boost::python; - - enum_<LocalCommunicationMode>("LocalCommunicationMode") - .value("START", START) - .value("WAIT", WAIT) - .value("BUFFER", BUFFER) - .export_values(); - - python_coupling::for_each_noncopyable_type< Stencils > ( internal::UniformBufferedSchemeExporter() ); - - def( "createUniformBufferedScheme", &internal::createUniformBufferedScheme<Stencils>, - ( ( arg("blockForest"), arg("stencilName"), arg("tag")=778 ) ) ); - -} - -template<typename Stencils> -void exportUniformDirectScheme() -{ - using namespace boost::python; - - python_coupling::for_each_noncopyable_type< Stencils > ( internal::UniformDirectSchemeExporter() ); - - def( "createUniformDirectScheme", &internal::createUniformDirectScheme<Stencils>, - ( ( arg("blockForest"), arg("stencilName"), arg("tag")=778 ) ) ); -} - - - -} // namespace blockforest -} // namespace walberla - - diff --git a/src/blockforest/python/Exports.cpp b/src/blockforest/python/Exports.cpp deleted file mode 100644 index 08873381e..000000000 --- a/src/blockforest/python/Exports.cpp +++ /dev/null @@ -1,327 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.cpp -//! \ingroup domain_decomposition -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "blockforest/StructuredBlockForest.h" -#include "blockforest/communication/UniformBufferedScheme.h" -#include "blockforest/Initialization.h" -#include "blockforest/SetupBlock.h" -#include "blockforest/SetupBlockForest.h" -#include "blockforest/loadbalancing/StaticCurve.h" - -#include "core/logging/Logging.h" -#include "core/StringUtility.h" -#include "domain_decomposition/StructuredBlockStorage.h" -#include "python_coupling/Manager.h" -#include "python_coupling/helper/ConfigFromDict.h" - -#include "stencil/D3Q7.h" -#include "stencil/D3Q19.h" -#include "stencil/D3Q27.h" - -#include <memory> - -#include <sstream> - -#ifdef _MSC_VER -# pragma warning(push) -// disable warning boost/python/raw_function.hpp(55): warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data -# pragma warning( disable : 4267 ) -#endif //_MSC_VER -#include <boost/python/raw_function.hpp> -#ifdef _MSC_VER -# pragma warning(pop) -#endif //_MSC_VER - - -using namespace boost::python; - - -namespace walberla { -namespace blockforest { - -using walberla::blockforest::communication::UniformBufferedScheme; - -bool checkForThreeTuple( object obj ) //NOLINT -{ - if( ! extract<tuple> ( obj ).check() ) - return false; - - tuple t = extract<tuple> ( obj ); - return len(t) == 3; -} - - -object python_createUniformBlockGrid(tuple args, dict kw) //NOLINT -{ - if( len(args) > 0 ) { - PyErr_SetString( PyExc_ValueError, "This function takes only keyword arguments" ); - throw boost::python::error_already_set(); - } - - using boost::python::stl_input_iterator; - - boost::python::list keys = kw.keys(); - for( auto it = stl_input_iterator<std::string>( keys ); it != stl_input_iterator<std::string>(); ++it ) - { - if ( *it != "cells" && - *it != "cellsPerBlock" && - *it != "blocks" && - *it != "periodic" && - *it != "dx" && - *it != "oneBlockPerProcess" ) - { - PyErr_SetString( PyExc_ValueError, (std::string("Unknown Parameter: ") + (*it) ).c_str() ); - throw boost::python::error_already_set(); - } - } - - if( kw.has_key("cells ") && ! checkForThreeTuple( kw["cells"] ) ) { - PyErr_SetString( PyExc_ValueError, "Parameter 'cells' has to be tuple of length 3, indicating cells in x,y,z direction" ); - throw boost::python::error_already_set(); - } - if( kw.has_key("cellsPerBlock ") && ! checkForThreeTuple( kw["cellsPerBlock"] ) ) { - PyErr_SetString( PyExc_ValueError, "Parameter 'cellsPerBlock' has to be tuple of length 3, indicating cells in x,y,z direction" ); - throw boost::python::error_already_set(); - } - if( kw.has_key("blocks ") && ! checkForThreeTuple( kw["blocks"] ) ) { - PyErr_SetString( PyExc_ValueError, "Parameter 'blocks' has to be tuple of length 3, indicating cells in x,y,z direction" ); - throw boost::python::error_already_set(); - } - - bool keepGlobalBlockInformation = false; - if ( kw.has_key("keepGlobalBlockInformation") ) - { - if ( extract<bool>( kw["keepGlobalBlockInformation"] ).check() ) - keepGlobalBlockInformation = extract<bool>( kw["keepGlobalBlockInformation"] ); - else - { - PyErr_SetString( PyExc_ValueError, "Parameter 'keepGlobalBlockInformation' has to be a boolean" ); - throw boost::python::error_already_set(); - } - } - - shared_ptr<Config> cfg = python_coupling::configFromPythonDict( kw ); - - try { - shared_ptr< StructuredBlockForest > blocks = createUniformBlockGridFromConfig( cfg->getGlobalBlock(), nullptr, keepGlobalBlockInformation ); - return object(blocks); - } - catch( std::exception & e) - { - PyErr_SetString( PyExc_ValueError, e.what() ); - throw boost::python::error_already_set(); - } - -} - -shared_ptr<StructuredBlockForest> createStructuredBlockForest( Vector3<uint_t> blocks, - Vector3<uint_t> cellsPerBlock, - Vector3<bool> periodic, - object blockExclusionCallback = object(), - object workloadMemoryCallback = object(), - object refinementCallback = object(), - const real_t dx = 1.0, - memory_t processMemoryLimit = std::numeric_limits<memory_t>::max(), - const bool keepGlobalBlockInformation = false) -{ - using namespace blockforest; - Vector3<real_t> bbMax; - for( uint_t i=0; i < 3; ++i ) - bbMax[i] = real_c( blocks[i] * cellsPerBlock[i] ) * dx; - AABB domainAABB ( Vector3<real_t>(0), bbMax ); - - SetupBlockForest sforest; - - auto blockExclusionFunc = [&blockExclusionCallback] ( std::vector<walberla::uint8_t>& excludeBlock, const SetupBlockForest::RootBlockAABB& aabb ) -> void - { - for( uint_t i = 0; i != excludeBlock.size(); ++i ) - { - AABB bb = aabb(i); - auto pythonReturnVal = blockExclusionCallback(bb); - if( ! extract<bool>( pythonReturnVal ).check() ) { - PyErr_SetString( PyExc_ValueError, "blockExclusionCallback has to return a boolean"); - throw boost::python::error_already_set(); - } - - bool returnVal = extract<bool>(pythonReturnVal); - if ( returnVal ) - excludeBlock[i] = 1; - } - }; - - auto workloadMemoryFunc = [&workloadMemoryCallback] ( SetupBlockForest & forest )-> void - { - std::vector< SetupBlock* > blockVector; - forest.getBlocks( blockVector ); - - for( uint_t i = 0; i != blockVector.size(); ++i ) { - blockVector[i]->setMemory( memory_t(1) ); - blockVector[i]->setWorkload( workload_t(1) ); - workloadMemoryCallback( boost::python::ptr(blockVector[i]) ); - } - }; - - auto refinementFunc = [&refinementCallback] ( SetupBlockForest & forest )-> void - { - for( auto block = forest.begin(); block != forest.end(); ++block ) - { - SetupBlock * sb = &(*block); - auto pythonRes = refinementCallback( boost::python::ptr(sb) ); - if( ! extract<bool>( pythonRes ).check() ) { - PyErr_SetString( PyExc_ValueError, "refinementCallback has to return a boolean"); - throw boost::python::error_already_set(); - } - bool returnVal = extract<bool>( pythonRes ); - if( returnVal ) - block->setMarker( true ); - } - }; - - if ( blockExclusionCallback ) { - if( !PyCallable_Check( blockExclusionCallback.ptr() ) ) { - PyErr_SetString( PyExc_ValueError, "blockExclusionCallback has to be callable"); - throw boost::python::error_already_set(); - } - sforest.addRootBlockExclusionFunction( blockExclusionFunc ); - } - - if ( workloadMemoryCallback ) { - if( !PyCallable_Check( workloadMemoryCallback.ptr() ) ) { - PyErr_SetString( PyExc_ValueError, "workloadMemoryCallback has to be callable"); - throw boost::python::error_already_set(); - } - sforest.addWorkloadMemorySUIDAssignmentFunction( workloadMemoryFunc ); - } - else - sforest.addWorkloadMemorySUIDAssignmentFunction( uniformWorkloadAndMemoryAssignment ); - - if ( refinementCallback ) { - if( !PyCallable_Check( refinementCallback.ptr() ) ) { - PyErr_SetString( PyExc_ValueError, "refinementCallback has to be callable"); - throw boost::python::error_already_set(); - } - sforest.addRefinementSelectionFunction( refinementFunc ); - } - - sforest.init( domainAABB, blocks[0], blocks[1], blocks[2], periodic[0], periodic[1], periodic[2] ); - - // calculate process distribution - sforest.balanceLoad( blockforest::StaticLevelwiseCurveBalanceWeighted(), - uint_c( MPIManager::instance()->numProcesses() ), - real_t(0), processMemoryLimit ); - - if( !MPIManager::instance()->rankValid() ) - MPIManager::instance()->useWorldComm(); - - // create StructuredBlockForest (encapsulates a newly created BlockForest) - auto bf = std::make_shared< BlockForest >( uint_c( MPIManager::instance()->rank() ), sforest, keepGlobalBlockInformation ); - - auto sbf = std::make_shared< StructuredBlockForest >( bf, cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2] ); - sbf->createCellBoundingBoxes(); - - return sbf; -} - -object createUniformNeighborScheme( const shared_ptr<StructuredBlockForest> & bf, - const std::string & stencil ) -{ - if ( string_icompare(stencil, "D3Q7") == 0 ) - return object ( make_shared< UniformBufferedScheme<stencil::D3Q7> > ( bf ) ); - if ( string_icompare(stencil, "D3Q19") == 0 ) - return object ( make_shared< UniformBufferedScheme<stencil::D3Q19> > ( bf ) ); - if ( string_icompare(stencil, "D3Q27") == 0 ) - return object ( make_shared< UniformBufferedScheme<stencil::D3Q27> > ( bf ) ); - else { - PyErr_SetString( PyExc_RuntimeError, "Unknown stencil. Allowed values 'D3Q27', 'D3Q19', 'D3Q7'"); - throw error_already_set(); - return object(); - } -} - -template<typename Stencil> -void exportUniformBufferedScheme() -{ - typedef UniformBufferedScheme<Stencil> UNS; - - class_< UNS, shared_ptr<UNS>, boost::noncopyable >( "UniformBufferedScheme", no_init ) - .def( "__call__", &UNS::operator() ) - .def( "communicate", &UNS::communicate ) - .def( "startCommunication", &UNS::startCommunication ) - .def( "wait", &UNS::wait ) - .def( "addPackInfo", &UNS::addPackInfo ) - .def( "addDataToCommunicate", &UNS::addDataToCommunicate ) - ; - -} - -std::string printSetupBlock(const SetupBlock & b ) -{ - std::stringstream out; - out << "SetupBlock at " << b.getAABB(); - return out.str(); -} - - - -void exportBlockForest() -{ - class_< StructuredBlockForest, //NOLINT - shared_ptr<StructuredBlockForest>, - bases<StructuredBlockStorage>, boost::noncopyable > ( "StructuredBlockForest", no_init ); - - class_< SetupBlock, boost::noncopyable > ( "SetupBlock", no_init ) - .add_property("level", &SetupBlock::getLevel) - .add_property("workload", &SetupBlock::getWorkload, &SetupBlock::setWorkload) - .add_property("memory", &SetupBlock::getMemory, &SetupBlock::setMemory) - .add_property("aabb", make_function(&SetupBlock::getAABB, return_value_policy<copy_const_reference>())) - .def("__repr__", &printSetupBlock) - ; - -#ifdef _MSC_VER -# pragma warning(push) -// disable warning boost/python/raw_function.hpp(55): warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data -# pragma warning( disable : 4267 ) -#endif //_MSC_VER - def( "createUniformBlockGrid", raw_function(python_createUniformBlockGrid) ); -#ifdef _MSC_VER -# pragma warning(pop) -#endif //_MSC_VER - - def( "createCustomBlockGrid", createStructuredBlockForest, - (arg("blocks"), arg("cellsPerBlock"), arg("periodic"), - arg("blockExclusionCallback") = object(), - arg("workloadMemoryCallback") = object(), - arg("refinementCallback") = object() , - arg("dx") = 1.0, - arg("processMemoryLimit") = std::numeric_limits<memory_t>::max(), - arg("keepGlobalBlockInformation") = false ) ); -} - -} // namespace domain_decomposition -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/blockforest/python/Exports.h b/src/blockforest/python/Exports.h deleted file mode 100644 index a910b0541..000000000 --- a/src/blockforest/python/Exports.h +++ /dev/null @@ -1,51 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.h -//! \ingroup blockforest -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" - - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "blockforest/python/CommunicationExport.h" - - -namespace walberla { -namespace blockforest { - - - void exportBlockForest(); - - - template<typename Stencils> - void exportModuleToPython() - { - exportBlockForest(); - exportUniformBufferedScheme<Stencils>(); - exportUniformDirectScheme<Stencils>(); - } - -} // namespace blockforest -} // namespace walberla - - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/boundary/python/Exports.h b/src/boundary/python/Exports.h deleted file mode 100644 index 2425f4788..000000000 --- a/src/boundary/python/Exports.h +++ /dev/null @@ -1,39 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace lbm { - - template<typename BoundaryHandlings> - void exportModuleToPython(); - -} // namespace lbm -} // namespace walberla - - -#include "Exports.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/boundary/python/Exports.impl.h b/src/boundary/python/Exports.impl.h deleted file mode 100644 index e6b5d85cb..000000000 --- a/src/boundary/python/Exports.impl.h +++ /dev/null @@ -1,232 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.impl.h -//! \ingroup boundary -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "python_coupling/Manager.h" -#include "python_coupling/helper/SliceToCellInterval.h" -#include "python_coupling/helper/ConfigFromDict.h" -#include "boundary/Boundary.h" -#include "field/FlagField.h" - - -namespace walberla { -namespace boundary { - - namespace internal - { - using python_coupling::localPythonSliceToCellInterval; - - template<typename BH> - shared_ptr<BoundaryConfiguration> boundaryConfFromDict( BH & h, const BoundaryUID & uid, boost::python::dict d ) { - shared_ptr<Config> cfg = python_coupling::configFromPythonDict( d ); - return h.createBoundaryConfiguration( uid, cfg->getGlobalBlock() ); - } - template<typename BH> - void BH_setBoundary1( BH & h, const std::string & name, cell_idx_t x, cell_idx_t y , cell_idx_t z, boost::python::dict conf ) { - h.setBoundary( name,x,y,z, *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_setBoundary2( BH & h, const std::string & name, const boost::python::tuple & index, boost::python::dict conf ) { - h.setBoundary( name, - localPythonSliceToCellInterval( *h.getFlagField() , index ), - *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_forceBoundary1( BH & h, const std::string & name, cell_idx_t x, cell_idx_t y , cell_idx_t z, boost::python::dict conf ) { - h.forceBoundary( name,x,y,z, *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_forceBoundary2( BH & h, const std::string & name, const boost::python::tuple & index, boost::python::dict conf ) { - h.forceBoundary( name, - localPythonSliceToCellInterval( *h.getFlagField() , index ), - *boundaryConfFromDict( h, name, conf) ); - } - - template<typename BH> - void BH_forceBoundary3( BH & h, const GhostLayerField<int,1> & indexField , boost::python::dict boundaryInfo ) - { - using namespace boost::python; - list keys = boundaryInfo.keys(); - - std::map<int, FlagUID > flagUIDs; - std::map<int, shared_ptr<BoundaryConfiguration> > boundaryConfigs; - - for (int i = 0; i < len( keys ); ++i) - { - int key = extract<int>( keys[i] ); - extract<std::string> extracted_str_val ( boundaryInfo[key] ); - extract<dict > extracted_dict_val ( boundaryInfo[key] ); - - if ( extracted_str_val.check() ) - { - std::string boundaryName = extracted_str_val; - flagUIDs[key] = FlagUID ( boundaryName ); - } - else if ( extracted_dict_val.check() ) - { - dict info = extracted_dict_val; - std::string boundaryName = extract<std::string>( info["name"] ); - - dict configDict = extract<dict>( info["config"] ); - - flagUIDs[key] = FlagUID ( boundaryName ); - boundaryConfigs[key] = boundaryConfFromDict( h, boundaryName, configDict); - } - else { - PyErr_SetString( PyExc_ValueError, "Invalid parameter"); - throw error_already_set(); - } - } - - if ( indexField.xyzSize() != h.getFlagField()->xyzSize() || indexField.nrOfGhostLayers() > h.getFlagField()->nrOfGhostLayers() ) { - PyErr_SetString( PyExc_ValueError, "Index field has to have same size as flag field"); - throw error_already_set(); - } - - // iterate over flag field - cell_idx_t gl = cell_idx_c( indexField.nrOfGhostLayers() ); - for( cell_idx_t z = -gl; z < cell_idx_c( indexField.zSize() ) + gl; ++z ) - for( cell_idx_t y = -gl; y < cell_idx_c( indexField.ySize() ) + gl; ++y ) - for( cell_idx_t x = -gl; x < cell_idx_c( indexField.xSize() ) + gl; ++x ) - { - int index = indexField(x,y,z); - if ( flagUIDs.find( index ) != flagUIDs.end() ) - { - if ( boundaryConfigs.find( index ) != boundaryConfigs.end() ) - h.forceBoundary( flagUIDs[index],x,y,z, * boundaryConfigs[index] ); - else - h.forceBoundary( flagUIDs[index],x,y,z ); - } - } - } - - template<typename BH> - void BH_setDomainSlice( BH & h, const boost::python::tuple & index ) { - h.setDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - template<typename BH> - void BH_forceDomainSlice( BH & h, const boost::python::tuple & index ) { - h.forceDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - template<typename BH> - void BH_fillDomainSlice( BH & h, const boost::python::tuple & index ) { - h.fillWithDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_removeDomainSlice( BH & h, const boost::python::tuple & index ) { - h.removeDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_removeBoundarySlice( BH & h, const boost::python::tuple & index ) { - h.removeBoundary( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_clearSlice( BH & h, const boost::python::tuple & index ) { - h.clear( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - - - struct BoundaryHandlingExporter - { - template< typename BH> - void operator() ( python_coupling::NonCopyableWrap<BH> ) - { - using namespace boost::python; - void ( BH::*p_exe1 )( uint_t )= &BH::operator(); - - bool ( BH::*p_isEmpty ) ( cell_idx_t, cell_idx_t, cell_idx_t ) const = &BH::isEmpty ; - bool ( BH::*p_isNearBoundary ) ( cell_idx_t, cell_idx_t, cell_idx_t ) const = &BH::isNearBoundary; - bool ( BH::*p_isBoundary ) ( cell_idx_t, cell_idx_t, cell_idx_t ) const = &BH::isBoundary ; - bool ( BH::*p_isDomain ) ( cell_idx_t, cell_idx_t, cell_idx_t ) const = &BH::isDomain ; - - void ( BH::*p_setDomain ) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::setDomain ; - void ( BH::*p_forceDomain ) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::forceDomain; - void ( BH::*p_fillWithDomain1) ( const uint_t ) = &BH::fillWithDomain; - void ( BH::*p_fillWithDomain2) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::fillWithDomain; - - void ( BH::*p_removeDomain1 ) ( const uint_t ) = &BH::removeDomain; - void ( BH::*p_removeDomain2 ) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::removeDomain; - - void ( BH::*p_removeBoundary1 ) ( const uint_t ) = &BH::removeBoundary; - void ( BH::*p_removeBoundary2 ) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::removeBoundary; - - void ( BH::*p_clear1 ) ( const uint_t ) = &BH::clear; - void ( BH::*p_clear2 ) ( cell_idx_t, cell_idx_t, cell_idx_t )= &BH::clear; - - typename BH::FlagField * ( BH::*p_getFlagField) () = &BH::getFlagField; - - typename BH::flag_t ( BH::*p_getNearBoundaryFlag ) () const = &BH::getNearBoundaryFlag; - typename BH::flag_t ( BH::*p_getBoundaryMask ) () const = &BH::getBoundaryMask; - typename BH::flag_t ( BH::*p_getDomainMask ) () const = &BH::getDomainMask; - - class_< BH, boost::noncopyable > ( "BoundaryHandling", no_init ) - .def( "__call__", p_exe1, ( arg("numberOfGhostLayersToInclude")=0 ) ) - .def( "isEmpty" , p_isEmpty, ( arg("x"), arg("y"), arg("z") ) ) - .def( "isNearBoundary", p_isNearBoundary, ( arg("x"), arg("y"), arg("z") ) ) - .def( "isBoundary" , p_isBoundary, ( arg("x"), arg("y"), arg("z") ) ) - .def( "isDomain" , p_isDomain, ( arg("x"), arg("y"), arg("z") ) ) - .def( "setDomain" , p_setDomain, ( arg("x"), arg("y"), arg("z") ) ) - .def( "setDomain" , BH_forceDomainSlice<BH>, ( arg("slice") ) ) - .def( "forceDomain" , p_forceDomain, ( arg("x"), arg("y"), arg("z") ) ) - .def( "forceDomain" , BH_forceDomainSlice<BH>, ( arg("slice") ) ) - .def( "fillWithDomain", p_fillWithDomain1, ( arg("numberOfGhostLayersToInclude")=0 ) ) - .def( "fillWithDomain", p_fillWithDomain2, ( arg("x"), arg("y"), arg("z") ) ) - .def( "fillWithDomain", BH_fillDomainSlice<BH>, ( arg("slice") ) ) - .def( "setBoundary", &BH_setBoundary1<BH>, ( arg("name"), arg("x"), arg("y"), arg("z"), arg("boundaryParams")=dict() ) ) - .def( "setBoundary", &BH_setBoundary2<BH>, ( arg("name"), arg("slice"), arg("boundaryParams")=dict() ) ) - .def( "forceBoundary", &BH_forceBoundary1<BH>, ( arg("name"), arg("x"), arg("y"), arg("z"), arg("boundaryParams")=dict() ) ) - .def( "forceBoundary", &BH_forceBoundary2<BH>, ( arg("name"), arg("slice"), arg("boundaryParams")=dict() ) ) - .def( "forceBoundary", &BH_forceBoundary3<BH>, ( arg("indexField"), arg("boundaryInfo") ) ) - .def( "removeDomain", p_removeDomain1, ( arg("numberOfGhostLayersToInclude")=0 ) ) - .def( "removeDomain", p_removeDomain2, ( arg("x"), arg("y"), arg("z") ) ) - .def( "removeDomain", BH_removeDomainSlice<BH>, ( arg("slice") ) ) - .def( "removeBoundary", p_removeBoundary1, ( arg("numberOfGhostLayersToInclude")=0 ) ) - .def( "removeBoundary", p_removeBoundary2, ( arg("x"), arg("y"), arg("z") ) ) - .def( "removeBoundary", BH_removeBoundarySlice<BH>,( arg("slice") ) ) - .def( "clear", p_clear1, ( arg("numberOfGhostLayersToInclude")=0 ) ) - .def( "clear", p_clear2, ( arg("x"), arg("y"), arg("z") ) ) - .def( "clear", BH_clearSlice<BH>, ( arg("slice") ) ) - .def( "getFlagField", p_getFlagField, return_internal_reference<>() ) - .def( "getNearBoundaryFlag", p_getNearBoundaryFlag ) - .def( "getBoundaryMask", p_getBoundaryMask ) - .def( "getDomainMask", p_getDomainMask ) - ; - } - }; - } // namespace internal - - template<typename BoundaryHandlings> - void exportModuleToPython() - { - python_coupling::for_each_noncopyable_type< BoundaryHandlings > ( internal::BoundaryHandlingExporter() ); - - auto pythonManager = python_coupling::Manager::instance(); - pythonManager->addBlockDataConversion< BoundaryHandlings> (); - - } - - -} // namespace boundary -} // namespace walberla - - diff --git a/src/communication/UniformMPIDatatypeInfo.h b/src/communication/UniformMPIDatatypeInfo.h index 33e44a873..be8689441 100644 --- a/src/communication/UniformMPIDatatypeInfo.h +++ b/src/communication/UniformMPIDatatypeInfo.h @@ -46,6 +46,14 @@ namespace communication { { public: + //**Construction & Destruction************************************************************************************ + /*! \name Construction & Destruction */ + //@{ + UniformMPIDatatypeInfo() {} + virtual ~UniformMPIDatatypeInfo() {} + //@} + //**************************************************************************************************************** + /*************************************************************************************************************//** * Return the MPI data type that should be used for sending to neighbor in specified direction *****************************************************************************************************************/ diff --git a/src/core/WeakPtrWrapper.h b/src/core/WeakPtrWrapper.h deleted file mode 100644 index 206042290..000000000 --- a/src/core/WeakPtrWrapper.h +++ /dev/null @@ -1,100 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file WeakPtrWrapper.h -//! \ingroup core -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#include "core/DataTypes.h" - - -/* - What is this good for? - - Memory Management of basic waLBerla objects: Use BlockStorage and CommunicationSchemes as example: - - a block storage is held as a shared_ptr - - create a communication scheme: internally the communication scheme - only holds a weak pointer to the block storage i.e. communication does not "own" the block storage. - If the communication would own the block storage and would internally hold a shared_ptr instead of a - weak_ptr, cycles could be created e.g. when a communication object is passed to the block storage, - which happens e.g. when registering communication functors at the block storage itself - -> weak_ptr is necessary to prevent cycles ( another way would be not to store a pointer to - block storage at all which is hardly practicable ) - - trying to communicate after a block storage was deleted leads to an error, which is reasonable - - The problem occurs when exporting this to Python: - - Due to a known and probably never fixed bug, one cannot created weak pointers from shared pointers - received from Python functions. Details here: - - http://stackoverflow.com/questions/8233252/boostpython-and-weak-ptr-stuff-disappearing - - https://svn.boost.org/trac/boost/ticket/3673 - - What works are plain old C pointers -> when compiling with Python plain C pointers should be used instead - of the checked weak pointers -> this is the reason for this wrapper class - - Special attention is necessary when exporting this construct to Python since a block forest may only be - deleted when no communication object points to it. This is ensured in this case via - UniformBufferedSchemeWrapper - */ - - - - - - - -namespace walberla { - - -#ifndef WALBERLA_BUILD_WITH_PYTHON - - - -template<typename T> -class weak_ptr_wrapper : public weak_ptr<T> -{ -public: - weak_ptr_wrapper() {} - weak_ptr_wrapper( const weak_ptr <T> & r ) : weak_ptr<T>(r) {} - weak_ptr_wrapper( const shared_ptr<T> & r ) : weak_ptr<T>(r) {} -}; - - -# else - - // Due to a bug in boost::python weak_ptr cannnot be used: - // http://stackoverflow.com/questions/8233252/boostpython-and-weak-ptr-stuff-disappearing - template<typename T> - class weak_ptr_wrapper - { - public: - weak_ptr_wrapper( const shared_ptr<T> & sp ) - : rawPtr_ ( sp.get() ) - {} - - T * lock() { return rawPtr_; } - - protected: - T * rawPtr_; - }; - - -#endif // WALBERLA_BUILD_WITH_PYTHON - -} // namespace walberla - - diff --git a/src/cuda/CMakeLists.txt b/src/cuda/CMakeLists.txt index 98aa991f0..b701f144f 100644 --- a/src/cuda/CMakeLists.txt +++ b/src/cuda/CMakeLists.txt @@ -4,7 +4,7 @@ # ################################################################################################### -waLBerla_add_module( DEPENDS blockforest core communication domain_decomposition executiontree python_coupling field stencil +waLBerla_add_module( DEPENDS blockforest core communication domain_decomposition executiontree field stencil BUILD_ONLY_IF_FOUND CUDA ) ################################################################################################### \ No newline at end of file diff --git a/src/cuda/communication/UniformGPUScheme.h b/src/cuda/communication/UniformGPUScheme.h index 0dda96a48..ba702c574 100644 --- a/src/cuda/communication/UniformGPUScheme.h +++ b/src/cuda/communication/UniformGPUScheme.h @@ -24,7 +24,6 @@ #include "blockforest/StructuredBlockForest.h" #include "core/mpi/MPIWrapper.h" #include "core/mpi/BufferSystem.h" -#include "core/WeakPtrWrapper.h" #include "domain_decomposition/IBlock.h" #include "stencil/Directions.h" @@ -45,7 +44,7 @@ namespace communication { class UniformGPUScheme { public: - explicit UniformGPUScheme( weak_ptr_wrapper<StructuredBlockForest> bf, + explicit UniformGPUScheme( weak_ptr<StructuredBlockForest> bf, bool sendDirectlyFromGPU = false, const int tag = 5432 ); @@ -60,7 +59,7 @@ namespace communication { private: void setupCommunication(); - weak_ptr_wrapper<StructuredBlockForest> blockForest_; + weak_ptr<StructuredBlockForest> blockForest_; uint_t forestModificationStamp_; bool setupBeforeNextCommunication_; diff --git a/src/cuda/communication/UniformGPUScheme.impl.h b/src/cuda/communication/UniformGPUScheme.impl.h index b39d9d054..03b65f3b5 100644 --- a/src/cuda/communication/UniformGPUScheme.impl.h +++ b/src/cuda/communication/UniformGPUScheme.impl.h @@ -27,7 +27,7 @@ namespace communication { template<typename Stencil> -UniformGPUScheme<Stencil>::UniformGPUScheme( weak_ptr_wrapper <StructuredBlockForest> bf, +UniformGPUScheme<Stencil>::UniformGPUScheme( weak_ptr <StructuredBlockForest> bf, bool sendDirectlyFromGPU, const int tag ) : blockForest_( bf ), diff --git a/src/cuda/python/Exports.impl.h b/src/cuda/python/Exports.impl.h deleted file mode 100644 index fa1352db9..000000000 --- a/src/cuda/python/Exports.impl.h +++ /dev/null @@ -1,407 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file FieldExport.cpp -//! \ingroup cuda -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#include "core/logging/Logging.h" -#include "cuda/GPUField.h" -#include "cuda/communication/GPUPackInfo.h" -#include "cuda/AddGPUFieldToStorage.h" -#include "cuda/FieldCopy.h" -#include "cuda/GPUField.h" -#include "field/communication/UniformMPIDatatypeInfo.h" -#include "field/AddToStorage.h" -#include "field/python/FieldExport.h" -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" - -#include <type_traits> -#include <iostream> - -namespace walberla { -namespace cuda { - - - -namespace internal { - - //=================================================================================================================== - // - // Field export - // - //=================================================================================================================== - - template<typename GpuField_T> - uint64_t gpufield_ptr(const GpuField_T & gpuField) - { - return reinterpret_cast<uint64_t>(gpuField.pitchedPtr().ptr); - } - - template<typename GpuField_T> - std::string gpufield_dtypeStr(const GpuField_T & ) - { - return std::string(field::internal::PythonFormatString<typename GpuField_T::value_type>::get()); - } - - struct GpuFieldExporter - { - template< typename GpuField_T> - void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) - { - using namespace boost::python; - - class_<GpuField_T, shared_ptr<GpuField_T>, boost::noncopyable>( "GpuField", no_init ) - .add_property("layout", &field::internal::field_layout < GpuField_T > ) - .add_property("size", &field::internal::field_size < GpuField_T > ) - .add_property("sizeWithGhostLayers", &field::internal::field_sizeWithGhostLayer< GpuField_T > ) - .add_property("allocSize", &field::internal::field_allocSize < GpuField_T > ) - .add_property("strides", &field::internal::field_strides < GpuField_T > ) - .add_property("offsets", &field::internal::field_offsets < GpuField_T > ) - .add_property("ptr", &gpufield_ptr < GpuField_T > ) - .add_property("dtypeStr", &gpufield_dtypeStr < GpuField_T > ) - .add_property("isPitchedMem", &GpuField_T::isPitchedMem ) - .def("swapDataPointers", &field::internal::field_swapDataPointers < GpuField_T > ) - .add_property("nrOfGhostLayers", &GpuField_T::nrOfGhostLayers ) - .def("cloneUninitialized", &GpuField_T::cloneUninitialized, return_value_policy<manage_new_object>()) - ; - - - using field::communication::PackInfo; - using communication::GPUPackInfo; - class_< GPUPackInfo<GpuField_T>, - shared_ptr< GPUPackInfo<GpuField_T> >, - bases<walberla::communication::UniformPackInfo>, - boost::noncopyable >( "GpuFieldPackInfo", no_init ); - - using field::communication::UniformMPIDatatypeInfo; - class_< UniformMPIDatatypeInfo<GpuField_T>, - shared_ptr< UniformMPIDatatypeInfo<GpuField_T> >, - bases<walberla::communication::UniformMPIDatatypeInfo>, - boost::noncopyable >( "GpuFieldMPIDataTypeInfo", no_init ); - - } - }; - - - //=================================================================================================================== - // - // createField - // - //=================================================================================================================== - - class CreateFieldExporter - { - public: - CreateFieldExporter( uint_t xs, uint_t ys, uint_t zs, uint_t fs, uint_t gl, - Layout layout, const boost::python::object & type, bool usePitchedMem, - const shared_ptr<boost::python::object> & resultPointer ) - : xs_( xs ), ys_(ys), zs_(zs), fs_(fs), gl_(gl), - layout_( layout), type_( type ), usePitchedMem_( usePitchedMem ) , resultPointer_( resultPointer ) - {} - - template< typename GpuField_T> - void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) - { - using namespace boost::python; - typedef typename GpuField_T::value_type T; - if( python_coupling::isCppEqualToPythonType<T>( (PyTypeObject *)type_.ptr() ) ) - { - *resultPointer_ = object( make_shared< GPUField<T> >( xs_,ys_,zs_, fs_, gl_, layout_, usePitchedMem_ ) ); - } - } - - private: - uint_t xs_; - uint_t ys_; - uint_t zs_; - uint_t fs_; - uint_t gl_; - Layout layout_; - boost::python::object type_; - bool usePitchedMem_; - shared_ptr<boost::python::object> resultPointer_; - }; - - template<typename GpuFields> - boost::python::object createPythonGpuField( boost::python::list size, - boost::python::object type, - uint_t ghostLayers, - Layout layout, - bool usePitchedMem) - { - using namespace boost::python; - uint_t xSize = extract<uint_t> ( size[0] ); - uint_t ySize = extract<uint_t> ( size[1] ); - uint_t zSize = extract<uint_t> ( size[2] ); - uint_t sizeLen = uint_c( len( size ) ); - uint_t fSize = 1; - if ( sizeLen == 4 ) - fSize = extract<uint_t> ( size[3] ); - - if ( ! PyType_Check( type.ptr() ) ) { - PyErr_SetString( PyExc_RuntimeError, "Invalid 'type' parameter"); - throw error_already_set(); - } - - auto result = make_shared<boost::python::object>(); - CreateFieldExporter exporter( xSize,ySize, zSize, fSize, ghostLayers, layout, type, usePitchedMem, result ); - python_coupling::for_each_noncopyable_type< GpuFields >( exporter ); - - if ( *result == object() ) - { - PyErr_SetString( PyExc_ValueError, "Cannot create field of this type"); - throw error_already_set(); - } - else { - return *result; - } - } - - - //=================================================================================================================== - // - // addToStorage - // - //=================================================================================================================== - - class AddToStorageExporter - { - public: - AddToStorageExporter( const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, uint_t fs, uint_t gl, Layout layout, - const boost::python::object & type, - bool usePitchedMem ) - : blocks_( blocks ), name_( name ), fs_( fs ), - gl_(gl),layout_( layout), type_( type ), usePitchedMem_(usePitchedMem), found_(false) - {} - - template< typename GpuField_T> - void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) - { - typedef typename GpuField_T::value_type T; - if( python_coupling::isCppEqualToPythonType<T>( (PyTypeObject *)type_.ptr() ) ) - { - WALBERLA_ASSERT(!found_); - addGPUFieldToStorage<GPUField<T> >(blocks_, name_, fs_, layout_, gl_, usePitchedMem_); - found_ = true; - } - } - - bool successful() const { return found_; } - private: - shared_ptr< StructuredBlockStorage > blocks_; - std::string name_; - uint_t fs_; - uint_t gl_; - Layout layout_; - boost::python::object type_; - bool usePitchedMem_; - bool found_; - }; - - template<typename GpuFields> - void addToStorage( const shared_ptr<StructuredBlockStorage> & blocks, const std::string & name, - boost::python::object type, uint_t fs, uint_t gl, Layout layout, bool usePitchedMem ) - { - using namespace boost::python; - - if ( ! PyType_Check( type.ptr() ) ) { - PyErr_SetString( PyExc_RuntimeError, "Invalid 'type' parameter"); - throw error_already_set(); - } - - auto result = make_shared<boost::python::object>(); - AddToStorageExporter exporter( blocks, name, fs, gl, layout, type, usePitchedMem ); - python_coupling::for_each_noncopyable_type<GpuFields>( std::ref(exporter) ); - - if ( ! exporter.successful() ) { - PyErr_SetString( PyExc_ValueError, "Adding Field failed."); - throw error_already_set(); - } - } - - - //=================================================================================================================== - // - // createPackInfo Export - // - //=================================================================================================================== - - template< typename GPUField_T > - boost::python::object createGPUPackInfoToObject( BlockDataID bdId, uint_t numberOfGhostLayers ) - { - using cuda::communication::GPUPackInfo; - if ( numberOfGhostLayers > 0 ) - return boost::python::object( make_shared< GPUPackInfo<GPUField_T> >( bdId, numberOfGhostLayers ) ); - else - return boost::python::object( make_shared< GPUPackInfo<GPUField_T> >( bdId ) ); - } - - FunctionExporterClass( createGPUPackInfoToObject, boost::python::object( BlockDataID, uint_t ) ); - - template< typename GpuFields> - boost::python::object createPackInfo( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName, uint_t numberOfGhostLayers ) - { - using cuda::communication::GPUPackInfo; - - auto bdId = python_coupling::blockDataIDFromString( *bs, blockDataName ); - if ( bs->begin() == bs->end() ) { - // if no blocks are on this field an arbitrary PackInfo can be returned - return createGPUPackInfoToObject< GPUField<real_t> > ( bdId, numberOfGhostLayers ); - } - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<GpuFields, Exporter_createGPUPackInfoToObject > dispatcher( firstBlock ); - return dispatcher( bdId )( bdId, numberOfGhostLayers ) ; - } - - - //=================================================================================================================== - // - // createMPIDatatypeInfo - // - //=================================================================================================================== - - - template< typename GpuField_T > - boost::python::object createMPIDatatypeInfoToObject( BlockDataID bdId, uint_t numberOfGhostLayers ) - { - using field::communication::UniformMPIDatatypeInfo; - if ( numberOfGhostLayers > 0 ) - return boost::python::object( make_shared< UniformMPIDatatypeInfo<GpuField_T> >( bdId, numberOfGhostLayers ) ); - else - return boost::python::object( make_shared< UniformMPIDatatypeInfo<GpuField_T> >( bdId ) ); - } - - FunctionExporterClass( createMPIDatatypeInfoToObject, boost::python::object( BlockDataID, uint_t ) ); - - template< typename GpuFields> - boost::python::object createMPIDatatypeInfo( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName, - uint_t numberOfGhostLayers) - { - auto bdId = python_coupling::blockDataIDFromString( *bs, blockDataName ); - if ( bs->begin() == bs->end() ) { - // if no blocks are on this field an arbitrary MPIDatatypeInfo can be returned - return createMPIDatatypeInfoToObject< GPUField<real_t> > ( bdId, numberOfGhostLayers ); - } - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<GpuFields, Exporter_createMPIDatatypeInfoToObject > dispatcher( firstBlock ); - return dispatcher( bdId )( bdId, numberOfGhostLayers ); - } - - - //=================================================================================================================== - // - // fieldCopy - // - //=================================================================================================================== - - template<typename Field_T> - void copyFieldToGpuDispatch(const shared_ptr<StructuredBlockStorage> & bs, - BlockDataID cpuFieldId, BlockDataID gpuFieldId, bool toGpu) - { - typedef cuda::GPUField<typename Field_T::value_type> GpuField; - if(toGpu) - cuda::fieldCpy<GpuField, Field_T>(bs, gpuFieldId, cpuFieldId); - else - cuda::fieldCpy<Field_T, GpuField>(bs, cpuFieldId, gpuFieldId); - } - FunctionExporterClass( copyFieldToGpuDispatch, - void( const shared_ptr<StructuredBlockStorage> &, BlockDataID, BlockDataID, bool ) ); - - template< typename FieldTypes > - void transferFields( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & gpuFieldId, const std::string & cpuFieldId, bool toGpu) - { - if( bs->begin() == bs->end()) { - return; - }; - - auto dstBdId = python_coupling::blockDataIDFromString( *bs, gpuFieldId ); - auto srcBdId = python_coupling::blockDataIDFromString( *bs, cpuFieldId ); - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<FieldTypes, Exporter_copyFieldToGpuDispatch> dispatcher( firstBlock ); - dispatcher( srcBdId )( bs, srcBdId, dstBdId, toGpu ); - } - - template< typename FieldTypes> - void copyFieldToGpu(const shared_ptr<StructuredBlockStorage> & bs, - const std::string & gpuFieldId, const std::string & cpuFieldId) - { - transferFields<FieldTypes>(bs, gpuFieldId, cpuFieldId, true); - } - - template< typename FieldTypes> - void copyFieldToCpu(const shared_ptr<StructuredBlockStorage> & bs, - const std::string & gpuFieldId, const std::string & cpuFieldId) - { - transferFields<FieldTypes>(bs, gpuFieldId, cpuFieldId, false); - } - -} // namespace internal - - - - -template<typename GpuFields, typename CpuFields > -void exportModuleToPython() -{ - python_coupling::ModuleScope fieldModule( "cuda" ); - - using namespace boost::python; - - python_coupling::for_each_noncopyable_type<GpuFields>( internal::GpuFieldExporter() ); - - def( "createGpuField", &internal::createPythonGpuField<GpuFields>, ( ( arg("size") ), - ( arg("type") ), - ( arg("ghostLayers") = uint_t(1) ), - ( arg("layout") = field::zyxf), - ( arg("usePitchedMem") = true ) ) ); - - - def( "addGpuFieldToStorage", &internal::addToStorage<GpuFields>, ( ( arg("blocks") ), - ( arg("name") ), - ( arg("type") ), - ( arg("fSize") = 1 ), - ( arg("ghostLayers") = uint_t(1) ), - ( arg("layout") = field::zyxf ), - ( arg("usePitchedMem") = object() ) ) ); - - def( "createMPIDatatypeInfo",&internal::createMPIDatatypeInfo<GpuFields>, ( arg("blocks"), arg("blockDataName"), arg("numberOfGhostLayers" ) =0 ) ); - def( "createPackInfo", &internal::createPackInfo<GpuFields>, ( arg("blocks"), arg("blockDataName"), arg("numberOfGhostLayers" ) =0 ) ); - - def( "copyFieldToGpu", &internal::copyFieldToGpu<CpuFields>, (arg("blocks"), ("gpuFieldId"), ("cpuFieldId"))); - def( "copyFieldToCpu", &internal::copyFieldToCpu<CpuFields>, (arg("blocks"), ("gpuFieldId"), ("cpuFieldId"))); -} - - - - - -} // namespace cuda -} // namespace walberla - - diff --git a/src/field/Traits.h b/src/field/Traits.h deleted file mode 100644 index bdde7f0c9..000000000 --- a/src/field/Traits.h +++ /dev/null @@ -1,61 +0,0 @@ - -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Traits.h -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - - -namespace walberla { -namespace field { - - - - - template<typename Pair> - struct ToField - { - typedef Field< typename Pair::first, Pair::second::value > type; - }; - - template<typename TupleList> - struct ToFieldList - { - typedef mpl::transform< TupleList, ToField<mpl::_1> >::type type; - }; - - template<typename Pair> - struct ToGhostLayerField - { - typedef GhostLayerField< typename Pair::first, Pair::second::value > type; - }; - - template<typename TupleList> - struct ToGhostLayerFieldList - { - typedef mpl::transform< TupleList, ToGhostLayerField<mpl::_1> >::type type; - }; - - -} // namespace field -} // namespace walberla - - - diff --git a/src/field/python/CommunicationExport.impl.h b/src/field/python/CommunicationExport.impl.h deleted file mode 100644 index 15066bcb0..000000000 --- a/src/field/python/CommunicationExport.impl.h +++ /dev/null @@ -1,260 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file CommunicationExport.impl.h -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "python_coupling/PythonWrapper.h" - - -#ifdef WALBERLA_BUILD_WITH_PYTHON - - -#include "field/communication/PackInfo.h" -#include "field/communication/StencilRestrictedPackInfo.h" -#include "field/communication/UniformMPIDatatypeInfo.h" - -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" -#include "python_coupling/helper/MplHelpers.h" - -#include "stencil/D2Q9.h" -#include "stencil/D3Q7.h" -#include "stencil/D3Q15.h" -#include "stencil/D3Q19.h" -#include "stencil/D3Q27.h" - - -namespace walberla { -namespace field { - - -namespace internal { - - //=================================================================================================================== - // - // createStencilRestrictedPackInfo Export - // - //=================================================================================================================== - - template< typename FieldType > - typename std::enable_if<FieldType::F_SIZE == 27, boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID bdId ) - { - typedef GhostLayerField<typename FieldType::value_type, 27> GlField_T; - using field::communication::StencilRestrictedPackInfo; - return boost::python::object( make_shared< StencilRestrictedPackInfo<GlField_T, stencil::D3Q27> >( bdId) ); - } - - template< typename FieldType > - typename std::enable_if<FieldType::F_SIZE == 19, boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID bdId ) - { - typedef GhostLayerField<typename FieldType::value_type, 19> GlField_T; - using field::communication::StencilRestrictedPackInfo; - return boost::python::object( make_shared< StencilRestrictedPackInfo<GlField_T, stencil::D3Q19> >( bdId) ); - } - - template< typename FieldType > - typename std::enable_if<FieldType::F_SIZE == 15, boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID bdId ) - { - typedef GhostLayerField<typename FieldType::value_type, 15> GlField_T; - using field::communication::StencilRestrictedPackInfo; - return boost::python::object( make_shared< StencilRestrictedPackInfo<GlField_T, stencil::D3Q15> >( bdId) ); - } - - template< typename FieldType > - typename std::enable_if<FieldType::F_SIZE == 7, boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID bdId ) - { - typedef GhostLayerField<typename FieldType::value_type, 7> GlField_T; - using field::communication::StencilRestrictedPackInfo; - return boost::python::object( make_shared< StencilRestrictedPackInfo<GlField_T, stencil::D3Q7> >( bdId) ); - } - - template< typename FieldType > - typename std::enable_if<FieldType::F_SIZE == 9, boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID bdId ) - { - typedef GhostLayerField<typename FieldType::value_type, 9> GlField_T; - using field::communication::StencilRestrictedPackInfo; - return boost::python::object( make_shared< StencilRestrictedPackInfo<GlField_T, stencil::D2Q9> >( bdId) ); - } - - template< typename FieldType > - typename std::enable_if<!(FieldType::F_SIZE == 9 || - FieldType::F_SIZE == 7 || - FieldType::F_SIZE == 15 || - FieldType::F_SIZE == 19 || - FieldType::F_SIZE == 27), boost::python::object>::type - createStencilRestrictedPackInfoObject( BlockDataID ) - { - PyErr_SetString( PyExc_ValueError, "This works only for fields with fSize in 7, 9, 15, 19 or 27" ); - throw boost::python::error_already_set(); - } - - FunctionExporterClass( createStencilRestrictedPackInfoObject, boost::python::object( BlockDataID ) ); - - template< typename FieldVector> - boost::python::object createStencilRestrictedPackInfo( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName ) - { - auto bdId = python_coupling::blockDataIDFromString( *bs, blockDataName ); - if ( bs->begin() == bs->end() ) { - // if no blocks are on this field an arbitrary PackInfo can be returned - return createStencilRestrictedPackInfoObject< GhostLayerField<real_t,1> > ( bdId ); - } - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<FieldVector, Exporter_createStencilRestrictedPackInfoObject > dispatcher( firstBlock ); - return dispatcher( bdId )( bdId ) ; - } - - //=================================================================================================================== - // - // createPackInfo Export - // - //=================================================================================================================== - - template< typename FieldType > - boost::python::object createPackInfoToObject( BlockDataID bdId, uint_t numberOfGhostLayers ) - { - typedef typename FieldType::value_type T; - const uint_t F_SIZE = FieldType::F_SIZE; - typedef GhostLayerField<T,F_SIZE> GlField_T; - if ( numberOfGhostLayers > 0 ) - return boost::python::object( make_shared< field::communication::PackInfo<GlField_T> >( bdId, numberOfGhostLayers ) ); - else - return boost::python::object( make_shared< field::communication::PackInfo<GlField_T> >( bdId ) ); - } - - FunctionExporterClass( createPackInfoToObject, boost::python::object( BlockDataID, uint_t ) ); - - template< typename FieldVector> - boost::python::object createPackInfo( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName, uint_t numberOfGhostLayers ) - { - auto bdId = python_coupling::blockDataIDFromString( *bs, blockDataName ); - if ( bs->begin() == bs->end() ) { - // if no blocks are on this field an arbitrary PackInfo can be returned - return createPackInfoToObject< GhostLayerField<real_t,1> > ( bdId, numberOfGhostLayers ); - } - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<FieldVector, Exporter_createPackInfoToObject > dispatcher( firstBlock ); - return dispatcher( bdId )( bdId, numberOfGhostLayers ) ; - } - - - //=================================================================================================================== - // - // createMPIDatatypeInfo - // - //=================================================================================================================== - - - template< typename FieldType > - boost::python::object createMPIDatatypeInfoToObject( BlockDataID bdId, uint_t numberOfGhostLayers ) - { - typedef typename FieldType::value_type T; - const uint_t F_SIZE = FieldType::F_SIZE; - typedef GhostLayerField<T,F_SIZE> GlField_T; - using field::communication::UniformMPIDatatypeInfo; - - if ( numberOfGhostLayers > 0 ) - return boost::python::object( make_shared< UniformMPIDatatypeInfo<GlField_T> >( bdId, numberOfGhostLayers ) ); - else - return boost::python::object( make_shared< UniformMPIDatatypeInfo<GlField_T> >( bdId ) ); - } - - FunctionExporterClass( createMPIDatatypeInfoToObject, boost::python::object( BlockDataID, uint_t ) ); - - template< typename FieldVector> - boost::python::object createMPIDatatypeInfo( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName, - uint_t numberOfGhostLayers) - { - auto bdId = python_coupling::blockDataIDFromString( *bs, blockDataName ); - if ( bs->begin() == bs->end() ) { - // if no blocks are on this field an arbitrary MPIDatatypeInfo can be returned - return createMPIDatatypeInfoToObject< GhostLayerField<real_t,1> > ( bdId, numberOfGhostLayers ); - } - - IBlock * firstBlock = & ( * bs->begin() ); - python_coupling::Dispatcher<FieldVector, Exporter_createMPIDatatypeInfoToObject > dispatcher( firstBlock ); - return dispatcher( bdId )( bdId, numberOfGhostLayers ); - } - - template< typename T> - void exportStencilRestrictedPackInfo() - { - using field::communication::StencilRestrictedPackInfo; - using namespace boost::python; - - { - typedef StencilRestrictedPackInfo<GhostLayerField<T, 9>, stencil::D2Q9> Pi; - class_< Pi, shared_ptr<Pi>, bases<walberla::communication::UniformPackInfo>, boost::noncopyable >( "StencilRestrictedPackInfo", no_init ); - } - { - typedef StencilRestrictedPackInfo<GhostLayerField<T, 7>, stencil::D3Q7> Pi; - class_< Pi, shared_ptr<Pi>, bases<walberla::communication::UniformPackInfo>, boost::noncopyable >( "StencilRestrictedPackInfo", no_init ); - } - { - typedef StencilRestrictedPackInfo<GhostLayerField<T, 15>, stencil::D3Q15> Pi; - class_< Pi, shared_ptr<Pi>, bases<walberla::communication::UniformPackInfo>, boost::noncopyable >( "StencilRestrictedPackInfo", no_init ); - } - { - typedef StencilRestrictedPackInfo<GhostLayerField<T, 19>, stencil::D3Q19> Pi; - class_< Pi, shared_ptr<Pi>, bases<walberla::communication::UniformPackInfo>, boost::noncopyable >( "StencilRestrictedPackInfo", no_init ); - } - { - typedef StencilRestrictedPackInfo<GhostLayerField<T, 27>, stencil::D3Q27> Pi; - class_< Pi, shared_ptr<Pi>, bases<walberla::communication::UniformPackInfo>, boost::noncopyable >( "StencilRestrictedPackInfo", no_init ); - } - - } - -} // namespace internal - - - - - -template<typename FieldTypes> -void exportCommunicationClasses() -{ - using namespace boost::python; - - internal::exportStencilRestrictedPackInfo<float>(); - internal::exportStencilRestrictedPackInfo<double>(); - - def( "createMPIDatatypeInfo",&internal::createMPIDatatypeInfo<FieldTypes>, ( arg("blocks"), arg("blockDataName"), arg("numberOfGhostLayers" ) =0 ) ); - def( "createPackInfo", &internal::createPackInfo<FieldTypes>, ( arg("blocks"), arg("blockDataName"), arg("numberOfGhostLayers" ) =0 ) ); - def( "createStencilRestrictedPackInfo", &internal::createStencilRestrictedPackInfo<FieldTypes>, - (arg("blocks"), arg("blockDataName") )); -} - - -} // namespace moduleName -} // namespace walberla - - - - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/field/python/FieldExport.h b/src/field/python/FieldExport.h deleted file mode 100644 index e7bdedb4f..000000000 --- a/src/field/python/FieldExport.h +++ /dev/null @@ -1,65 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file FieldExport.h -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#ifdef WALBERLA_BUILD_WITH_PYTHON - - -#include <string> - -namespace walberla { -namespace field { - - - //******************************************************************************************************************* - /*! Exports all Fields given in the Sequence - * - * Put only Fields in the sequence! The corresponding GhostLayerFields and FlagFields are exported automatically - * - * \warning Make sure that the same adaptor type is exported only once! - */ - //******************************************************************************************************************* - template<typename FieldTypes > - void exportFields(); - - - - //******************************************************************************************************************* - /*! Exports all GhostLayerFieldAdaptors given in the Sequence - * - * \warning Make sure that the same adaptor type is exported only once! - */ - //******************************************************************************************************************* - template<typename AdaptorTypes> - void exportGhostLayerFieldAdaptors(); - - template<typename AdaptorType> - void exportGhostLayerFieldAdaptor(); - - -} // namespace field -} // namespace walberla - -#include "FieldExport.impl.h" - - -#endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/field/python/FieldExport.impl.h b/src/field/python/FieldExport.impl.h deleted file mode 100644 index 83e3b4239..000000000 --- a/src/field/python/FieldExport.impl.h +++ /dev/null @@ -1,1396 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file FieldExport.cpp -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - - -#include "core/logging/Logging.h" -#include "core/VectorTrait.h" -#include "field/Field.h" -#include "field/GhostLayerField.h" -#include "field/FlagField.h" -#include "field/communication/PackInfo.h" -#include "field/communication/UniformMPIDatatypeInfo.h" - -#include "field/AddToStorage.h" -#include "field/python/GatherExport.h" -#include "field/vtk/VTKWriter.h" -#include "field/vtk/FlagFieldMapping.h" - -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" - -#include <boost/mpl/vector.hpp> - -#include <iostream> -#include <type_traits> - -namespace walberla { -namespace field { - - - -namespace internal { - - //=================================================================================================================== - // - // Buffer Protocol for Fields - // - //=================================================================================================================== - - - - /* This section implements the Python buffer protocol for walberla::Field's - * - * - Why? The buffer protocol enables other Python types to use the memory belonging to a field. - * One can for example construct a numpy.array that operates on the field data - * - How? a = numpy.asarray( myWalberlaField.buffer() ) - * creates a numpy array which uses the field data in a read-write way! no data is copied - * - Python buffer protocol: http://docs.python.org/dev/c-api/buffer.html - * - Why so complicated? - * boost::python does not yet (as in version 1.55) support the buffer protocol - * so everything has to be written in the native Python C interface. - * In order to export the Field with boost python and keep the native C interface part as - * small as possible, a new Type called FieldBuffer is introduced which is exported in the - * C interface. This type can only be created using the buffer() function of the field. - * - * - * - Lifetime issues: - * 0) f = walberla.create_field( ( 5,3,2) ) - * 1) buf = f.buffer(): - * - creates a FieldBuffer object which has as only member the Field - * - f is not deallocated as long as 'buf' exists - * 2) a = numpy.asarray( buf ) - * - calls 'fieldbuffer_get' which creates a Py_buffer, - * extracts the information, then immediately calls 'fieldbuffer_release' - * to clean up the Py_buffer. - * - buf still exists ( fieldbuffer_release only cleans up Py_buffer not - * the FieldBuffer object 'buf' ) - * 3) del f - * del b - * - the buffer object b is not deallocated since a still has a reference to it - * - since b is not deleted f is not deleted since b has still a reference to it - * i.e a -> b -> f - * 4) when a is deleted everything can be cleaned up, which means that fieldbuffer_dealloc - * is called, which decrements the reference count for f, so that f can be deallocated if - * not used otherwise - * - */ - template<class T> struct PythonFormatString { inline static char * get() { static char value [] = "B"; return value; } }; - - template<> struct PythonFormatString<double> { inline static char * get() { static char value [] = "d"; return value; } }; - template<> struct PythonFormatString<float> { inline static char * get() { static char value [] = "f"; return value; } }; - template<> struct PythonFormatString<unsigned short> { inline static char * get() { static char value [] = "H"; return value; } }; - template<> struct PythonFormatString<int> { inline static char * get() { static char value [] = "i"; return value; } }; - template<> struct PythonFormatString<unsigned int> { inline static char * get() { static char value [] = "I"; return value; } }; - template<> struct PythonFormatString<long> { inline static char * get() { static char value [] = "l"; return value; } }; - template<> struct PythonFormatString<unsigned long> { inline static char * get() { static char value [] = "L"; return value; } }; - template<> struct PythonFormatString<long long> { inline static char * get() { static char value [] = "q"; return value; } }; - template<> struct PythonFormatString<unsigned long long>{ inline static char * get() { static char value [] = "Q"; return value; } }; - - - typedef struct { - PyObject_HEAD - PyObject * field; - PyObject * fieldAlloc; - void * fieldData; - } FieldBufferPyObject; - - - template<typename T, uint_t fs> - struct FieldBufferGetDispatch - { - static int get( FieldBufferPyObject * exporter, Py_buffer * view, int flags, bool withGhostLayer ) - { - namespace bp = boost::python; - - bp::object fieldObject ( bp::handle<>(bp::borrowed( exporter->field ) ) ); - Field<T,fs> * field = bp::extract< Field<T,fs> * > ( fieldObject ); - - bp::object fieldAllocObject ( bp::handle<>(bp::borrowed( exporter->fieldAlloc ) ) ); - FieldAllocator<T> * fieldAlloc = bp::extract< FieldAllocator<T> * > ( fieldAllocObject ); - fieldAlloc->incrementReferenceCount( field->data() ); - - view->obj = (PyObject*) exporter; - Py_INCREF( view->obj ); - - uint_t size[3]; - auto glField = dynamic_cast<GhostLayerField<T,fs> * >( field ); - if ( glField && withGhostLayer ) - { - size[0] = glField->xSizeWithGhostLayer(); - size[1] = glField->ySizeWithGhostLayer(); - size[2] = glField->zSizeWithGhostLayer(); - cell_idx_t gl = cell_idx_c( glField->nrOfGhostLayers() ); - view->buf = &( field->get( -gl, -gl, -gl,0 ) ); - } - else - { - size[0] = field->xSize(); - size[1] = field->ySize(); - size[2] = field->zSize(); - view->buf = & ( field->get(0,0,0,0) ) ; - } - - - // Mandatory - view->len = Py_ssize_t( size[0] * size[1] * size[2] * fs * sizeof(T) ); - view->itemsize = sizeof( T ); - - view->ndim = 4; - - view->format = NULL; - if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) - view->format = PythonFormatString<T>::get(); - - view->shape=NULL; - if ((flags & PyBUF_ND) == PyBUF_ND) - { - view->shape = new Py_ssize_t[4]; - view->shape[0u] = Py_ssize_t( size[0u] ); - view->shape[1u] = Py_ssize_t( size[1u] ); - view->shape[2u] = Py_ssize_t( size[2u] ); - view->shape[3u] = Py_ssize_t( field->fSize() ); - } - - view->strides = NULL; - if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) - { - view->strides = new Py_ssize_t[4u]; - view->strides[0u] = Py_ssize_t( uint_c( field->xStride() ) * sizeof(T) ); - view->strides[1u] = Py_ssize_t( uint_c( field->yStride() ) * sizeof(T) ); - view->strides[2u] = Py_ssize_t( uint_c( field->zStride() ) * sizeof(T) ); - view->strides[3u] = Py_ssize_t( uint_c( field->fStride() ) * sizeof(T) ); - } - - view->suboffsets = NULL; - view->internal = NULL; - view->readonly = false; - - // We don't need the field any more. For freeing only the allocator and the field data is necessary - Py_DECREF( exporter->field ); - exporter->field = NULL; - - return 0; - } - }; - - template<typename VectorType> - struct FieldBufferGetDispatch<VectorType,1> - { - static int get( FieldBufferPyObject * exporter, Py_buffer * view, int flags, bool withGhostLayer ) - { - namespace bp = boost::python; - - typedef VectorTrait<VectorType> VecTrait; - typedef typename VecTrait::OutputType ElementType; - - static_assert( sizeof(VectorType) == VecTrait::F_SIZE*sizeof(ElementType), - "Creating Python Memory View works only for vectors types that hold their elements consecutively in memory" ); - - bp::object fieldObject ( bp::handle<>(bp::borrowed( exporter->field ) ) ); - Field<VectorType,1> * field = bp::extract< Field<VectorType,1> * > ( fieldObject ); - - bp::object fieldAllocObject ( bp::handle<>(bp::borrowed( exporter->fieldAlloc ) ) ); - FieldAllocator<VectorType> * fieldAlloc = bp::extract< FieldAllocator<VectorType> * > ( fieldAllocObject ); - fieldAlloc->incrementReferenceCount( field->data() ); - - view->obj = (PyObject*) exporter; - Py_INCREF( view->obj ); - - uint_t size[3]; - auto glField = dynamic_cast<GhostLayerField<VectorType,1> * >( field ); - if ( glField && withGhostLayer ) - { - size[0] = glField->xSizeWithGhostLayer(); - size[1] = glField->ySizeWithGhostLayer(); - size[2] = glField->zSizeWithGhostLayer(); - cell_idx_t gl = cell_idx_c( glField->nrOfGhostLayers() ); - view->buf = &( field->get( -gl, -gl, -gl,0 ) ); - } - else - { - size[0] = field->xSize(); - size[1] = field->ySize(); - size[2] = field->zSize(); - view->buf = & ( field->get(0,0,0,0) ) ; - } - - - // Mandatory - view->len = Py_ssize_t( size[0] * size[1] * size[2] * VecTrait::F_SIZE * sizeof(ElementType) ); - view->itemsize = sizeof( ElementType ); - - view->ndim = 4; - - view->format = NULL; - if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) - view->format = PythonFormatString<ElementType>::get(); - - view->shape=NULL; - if ((flags & PyBUF_ND) == PyBUF_ND) - { - view->shape = new Py_ssize_t[4]; - view->shape[0u] = Py_ssize_t( size[0u] ); - view->shape[1u] = Py_ssize_t( size[1u] ); - view->shape[2u] = Py_ssize_t( size[2u] ); - view->shape[3u] = Py_ssize_t( VecTrait::F_SIZE ); - } - - view->strides = NULL; - if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) - { - view->strides = new Py_ssize_t[4u]; - view->strides[0u] = Py_ssize_t( uint_c( field->xStride() ) * VecTrait::F_SIZE * sizeof(ElementType) ); - view->strides[1u] = Py_ssize_t( uint_c( field->yStride() ) * VecTrait::F_SIZE * sizeof(ElementType) ); - view->strides[2u] = Py_ssize_t( uint_c( field->zStride() ) * VecTrait::F_SIZE * sizeof(ElementType) ); - view->strides[3u] = Py_ssize_t( sizeof(ElementType) ); - } - - view->suboffsets = NULL; - view->internal = NULL; - view->readonly = false; - - // We don't need the field any more. For freeing only the allocator and the field data is necessary - Py_DECREF( exporter->field ); - exporter->field = NULL; - - return 0; - } - }; - - template<typename T, uint_t fs> - int fieldbuffer_get_withGl ( FieldBufferPyObject * exporter, Py_buffer * view, int flags ) - { - return FieldBufferGetDispatch<T,fs>::get( exporter, view, flags, true ); - } - - template<typename T, uint_t fs> - int fieldbuffer_get ( FieldBufferPyObject * exporter, Py_buffer * view, int flags ) - { - return FieldBufferGetDispatch<T,fs>::get( exporter, view, flags, false ); - } - - - template<typename T, uint_t fs> - int fieldbuffer_release ( PyObject * /*exporter*/, Py_buffer * view ) - { - delete [] view->strides; - delete [] view->shape; - //std::cout << "Releasing Field Buffer " << std::endl; - return 0; - } - - - template<typename T, uint_t fs> - static void fieldbuffer_dealloc( FieldBufferPyObject * exporter ) - { - namespace bp = boost::python; - bp::object fieldAllocObject ( bp::handle<>(bp::borrowed( exporter->fieldAlloc ) ) ); - FieldAllocator<T> * fieldAlloc = bp::extract< FieldAllocator<T> * > ( fieldAllocObject ); - - fieldAlloc->decrementReferenceCount( (T*) exporter->fieldData ); - - Py_DECREF( exporter->fieldAlloc ); - - - //std::cout << "Dealloc Fieldbuffer " << (void*) exporter << std::endl; - Py_TYPE(exporter)->tp_free ((PyObject*) exporter ); - } - - - template<typename T, uint_t fs> - Py_ssize_t fieldbuffer_getbuffer(FieldBufferPyObject *, Py_ssize_t , const void **) - { - WALBERLA_CHECK(false, "fieldbuffer_getbuffer is part of the old buffer interface and should never be used"); - return Py_ssize_t(0);// prevent compiler warning - } - - - template<typename T, uint_t fs> - Py_ssize_t fieldbuffer_getsegcount(FieldBufferPyObject *, Py_ssize_t *) - { - WALBERLA_CHECK(false, "fieldbuffer_getsegcount is part of the old buffer interface and should never be used"); - return Py_ssize_t(0);// prevent compiler warning - } - - - template<typename T, uint_t fs> - Py_ssize_t fieldbuffer_getbuffer_withGl(FieldBufferPyObject *, Py_ssize_t , const void **) - { - WALBERLA_CHECK(false, "fieldbuffer_getbuffer is part of the old buffer interface and should never be used"); - return Py_ssize_t(0);// prevent compiler warning - } - - - template<typename T, uint_t fs> - Py_ssize_t fieldbuffer_getsegcount_withGl(FieldBufferPyObject *, Py_ssize_t *) - { - WALBERLA_CHECK(false, "fieldbuffer_getsegcount is part of the old buffer interface and should never be used"); - return Py_ssize_t(0);// prevent compiler warning - } - - - - #ifdef WALBERLA_CXX_COMPILER_IS_GNU - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - #endif - - - template<typename T, uint_t fs> - struct FieldBufferType - { - static PyBufferProcs bufferProcs; - static PyTypeObject value; - }; - - template<typename T, uint_t fs> - PyBufferProcs FieldBufferType<T,fs>::bufferProcs = { -#if PY_MAJOR_VERSION < 3 - (readbufferproc) fieldbuffer_getbuffer <T,fs>, /* bf_getreadbuffer */ - (writebufferproc) fieldbuffer_getbuffer <T,fs>, /* bf_getwritebuffer */ - (segcountproc) fieldbuffer_getsegcount<T,fs>, /* bf_getsegcount */ - (charbufferproc) fieldbuffer_getbuffer <T,fs>, /* bf_getcharbuffer */ -#endif - (getbufferproc) fieldbuffer_get <T,fs>, /* bf_getbuffer */ - (releasebufferproc)fieldbuffer_release<T,fs>, /* bf_releasebuffer */ - }; - - template<typename T, uint_t fs> - PyTypeObject FieldBufferType<T,fs>::value = { - PyVarObject_HEAD_INIT(NULL, 0) - "walberla_cpp.FieldBuffer", /* tp_name */ - sizeof(FieldBufferPyObject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)fieldbuffer_dealloc<T,fs>, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - &FieldBufferType<T,fs>::bufferProcs, /* tp_as_buffer */ -#if PY_MAJOR_VERSION >= 3 - Py_TPFLAGS_DEFAULT, /* tp_flags */ -#else - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ -#endif - "FieldBuffer Objects", /* tp_doc */ - }; - - - - template<typename T, uint_t fs> - struct FieldBufferTypeGl - { - static PyBufferProcs bufferProcs; - static PyTypeObject value; - }; - - template<typename T, uint_t fs> - PyBufferProcs FieldBufferTypeGl<T,fs>::bufferProcs = { -#if PY_MAJOR_VERSION < 3 - (readbufferproc) fieldbuffer_getbuffer_withGl <T,fs>, /* bf_getreadbuffer */ - (writebufferproc) fieldbuffer_getbuffer_withGl <T,fs>, /* bf_getwritebuffer */ - (segcountproc) fieldbuffer_getsegcount_withGl<T,fs>, /* bf_getsegcount */ - (charbufferproc) fieldbuffer_getbuffer_withGl <T,fs>, /* bf_getcharbuffer */ -#endif - (getbufferproc) fieldbuffer_get_withGl<T,fs>, /* bf_getbuffer */ - (releasebufferproc)fieldbuffer_release <T,fs>, /* bf_releasebuffer */ - }; - - template<typename T, uint_t fs> - PyTypeObject FieldBufferTypeGl<T,fs>::value = { - PyVarObject_HEAD_INIT(NULL, 0) - "walberla_cpp.FieldBufferGl", /* tp_name */ - sizeof(FieldBufferPyObject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)fieldbuffer_dealloc<T,fs>, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - &FieldBufferTypeGl<T,fs>::bufferProcs, /* tp_as_buffer */ -#if PY_MAJOR_VERSION >= 3 - Py_TPFLAGS_DEFAULT, /* tp_flags */ -#else - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ -#endif - "FieldBufferGl Objects", /* tp_doc */ - }; - - - - #ifdef WALBERLA_CXX_COMPILER_IS_GNU - #pragma GCC diagnostic pop - #endif - - // this will become the buffer() function of the field which creates the - // FieldBuffer object, so this function is between field (exported in boost::python) and - // FieldBuffer which is exported in native Python C API - template<typename T, uint_t fs> - boost::python::object field_getBufferInterface( boost::python::object field, bool withGhostLayers ) - { - namespace bp = boost::python; - - FieldBufferPyObject *obj; - if ( withGhostLayers ) - obj = (FieldBufferPyObject*) PyObject_CallObject((PyObject *) & FieldBufferTypeGl<T,fs>::value, NULL ); - else - obj = (FieldBufferPyObject*) PyObject_CallObject((PyObject *) & FieldBufferType<T,fs>::value, NULL ); - - - Field<T,fs> * fieldPtr = bp::extract< Field<T,fs> * > ( field ); - bp::object fieldPtrObject( fieldPtr->getAllocator() ); - - obj->field = field.ptr(); - obj->fieldAlloc = fieldPtrObject.ptr(); - obj->fieldData = (void*) ( fieldPtr->data() ); - Py_INCREF( obj->field ); - Py_INCREF( obj->fieldAlloc ); - - return bp::object ( bp::handle<>( (PyObject*) obj ) ); - } - - - //=================================================================================================================== - // - // Aligned Allocation - // - //=================================================================================================================== - - template<typename T> - shared_ptr<field::FieldAllocator<T> > getAllocator(uint_t alignment) - { - if( alignment == 0 ) - return shared_ptr<field::FieldAllocator<T> >(); // leave to default - auto-detection of alignment - else if ( alignment == 16 ) - return make_shared< field::AllocateAligned<T, 16> >(); - else if ( alignment == 32 ) - return make_shared< field::AllocateAligned<T, 32> >(); - else if ( alignment == 64 ) - return make_shared< field::AllocateAligned<T, 64> >(); - else if ( alignment == 128 ) - return make_shared< field::AllocateAligned<T, 128> >(); - else { - PyErr_SetString( PyExc_ValueError, "Alignment parameter has to be one of 0, 16, 32, 64, 128." ); - throw boost::python::error_already_set(); - return shared_ptr<field::FieldAllocator<T> >(); - } - } - - template< typename GhostLayerField_T > - class GhostLayerFieldDataHandling : public field::BlockDataHandling< GhostLayerField_T > - { - public: - typedef typename GhostLayerField_T::value_type Value_T; - - GhostLayerFieldDataHandling( const weak_ptr<StructuredBlockStorage> &blocks, const uint_t nrOfGhostLayers, - const Value_T &initValue, const Layout layout, uint_t alignment = 0 ) : - blocks_( blocks ), nrOfGhostLayers_( nrOfGhostLayers ), initValue_( initValue ), layout_( layout ), - alignment_( alignment ) {} - - GhostLayerField_T * allocate( IBlock * const block ) - { - auto blocks = blocks_.lock(); - WALBERLA_CHECK_NOT_NULLPTR( blocks, "Trying to access 'AlwaysInitializeBlockDataHandling' for a block " - "storage object that doesn't exist anymore" ); - GhostLayerField_T * field = new GhostLayerField_T ( blocks->getNumberOfXCells( *block ), - blocks->getNumberOfYCells( *block ), - blocks->getNumberOfZCells( *block ), - nrOfGhostLayers_, initValue_, layout_, - getAllocator<Value_T>(alignment_) ); - return field; - } - - GhostLayerField_T * reallocate( IBlock * const block ) - { - return allocate(block); - } - - private: - weak_ptr< StructuredBlockStorage > blocks_; - - uint_t nrOfGhostLayers_; - Value_T initValue_; - Layout layout_; - uint_t alignment_; - }; - - - //=================================================================================================================== - // - // Field functions redefined for easier export - // - //=================================================================================================================== - - - static inline Cell tupleToCell( boost::python::tuple & tuple ) - { - using boost::python::extract; - return Cell ( extract<cell_idx_t>( tuple[0] ), - extract<cell_idx_t>( tuple[1] ), - extract<cell_idx_t>( tuple[2] ) ); - } - - template<typename Field_T> - void field_setCellXYZ( Field_T & field, boost::python::tuple args, const typename Field_T::value_type & value ) - { - using namespace boost::python; - - if ( len(args) < 3 || len(args) > 4 ) - { - PyErr_SetString( PyExc_RuntimeError, "3 or 4 indices required"); - throw error_already_set(); - } - - cell_idx_t f = 0; - if ( len(args) == 4 ) - f = extract<cell_idx_t> ( args[3] ); - - Cell cell = tupleToCell(args); - if ( ! field.coordinatesValid( cell[0], cell[1], cell[2], f ) ) - { - PyErr_SetString( PyExc_IndexError, "Field indices out of bounds"); - throw error_already_set(); - } - field(cell, f) = value; - } - - template<typename Field_T> - typename Field_T::value_type field_getCellXYZ( Field_T & field, boost::python::tuple args ) - { - using namespace boost::python; - if ( len(args) < 3 || len(args) > 4 ) - { - PyErr_SetString( PyExc_RuntimeError, "3 or 4 indices required"); - throw error_already_set(); - } - - cell_idx_t f = 0; - if ( len(args) == 4 ) - f = extract<cell_idx_t> ( args[3] ); - - Cell cell = tupleToCell(args); - if ( ! field.coordinatesValid( cell[0], cell[1], cell[2], f ) ) - { - PyErr_SetString( PyExc_IndexError, "Field indices out of bounds"); - throw error_already_set(); - } - - return field( cell, f ); - } - - - template<typename Field_T> - boost::python::object field_size( const Field_T & field ) { - return boost::python::make_tuple( field.xSize(), field.ySize(), field.zSize(), field.fSize() ); - } - - template<typename GlField_T> - boost::python::object field_sizeWithGhostLayer( const GlField_T & field ) { - return boost::python::make_tuple( field.xSizeWithGhostLayer(), field.ySizeWithGhostLayer(), field.zSizeWithGhostLayer(), field.fSize() ); - } - - - template<typename Field_T> - boost::python::object field_allocSize( const Field_T & field ) { - return boost::python::make_tuple( field.xAllocSize(), field.yAllocSize(), - field.zAllocSize(), field.fAllocSize() ); - } - - - template<typename Field_T> - boost::python::object field_strides( const Field_T & field ) { - return boost::python::make_tuple( field.xStride(), field.yStride(), - field.zStride(), field.fStride() ); - } - - template<typename Field_T> - boost::python::object field_offsets( const Field_T & field ) { - return boost::python::make_tuple( field.xOff(), field.yOff(), field.zOff() ); - } - - - template<typename Field_T> - boost::python::object field_layout( const Field_T & f ) { - if ( f.layout() == field::fzyx ) return boost::python::object( "fzyx" ); - if ( f.layout() == field::zyxf ) return boost::python::object( "zyxf" ); - - return boost::python::object(); - } - - - template<typename Field_T> - void field_swapDataPointers( Field_T & f1, Field_T & f2 ) - { - if ( ! f1.hasSameAllocSize(f2 ) || - ! f1.hasSameSize( f2) || - f1.layout() != f2.layout() ) - { - PyErr_SetString( PyExc_ValueError, "The data of fields with different sizes or layout cannot be swapped"); - throw boost::python::error_already_set(); - } - f1.swapDataPointers( f2 ); - } - - - template<typename T> - T FF_getFlag( const FlagField<T> & ff, const std::string & flag ) { - if ( ! ff.flagExists(flag) ) - { - PyErr_SetString( PyExc_ValueError, "No such flag"); - throw boost::python::error_already_set(); - } - return ff.getFlag( flag ); - } - - template<typename T> - boost::python::object FF_registeredFlags( const FlagField<T> & ff ) - { - std::vector<FlagUID> flags; - ff.getAllRegisteredFlags( flags ); - boost::python::list result; - - for( auto i = flags.begin(); i != flags.end(); ++i ) - result.append( i->toString() ); - boost::python::object objectResult = result; - return objectResult; - } - - - template<typename T> - boost::python::object FF_flagMap( const FlagField<T> & ff ) - { - std::vector<FlagUID> flags; - ff.getAllRegisteredFlags( flags ); - boost::python::dict result; - - for( auto i = flags.begin(); i != flags.end(); ++i ) - result[ i->toString() ] = ff.getFlag( *i ); - boost::python::object objectResult = result; - return objectResult; - } - - template<typename T> - boost::python::object FF_registerFlag( FlagField<T> & ff, const std::string & flag, boost::python::object bitNr ) - { - using namespace boost::python; - - try { - - if ( bitNr == object() ) - return object( ff.registerFlag( FlagUID(flag) ) ); - else - { - if ( extract<uint_t>(bitNr ).check() ) { - uint_t bit = extract<uint_t>(bitNr); - return object( ff.registerFlag( flag, bit ) ); - } - else { - PyErr_SetString( PyExc_ValueError, "Parameter bitNr has to be a positive integer"); - throw boost::python::error_already_set(); - } - } - } - catch ( std::runtime_error & e ) { - PyErr_SetString( PyExc_ValueError, e.what() ); - throw boost::python::error_already_set(); - } - - } - - template<typename T> - std::string FF_getFlagName( const FlagField<T> & ff, T flag ) - { - try { - return ff.getFlagUID( flag ).getIdentifier(); - } - catch ( std::runtime_error & e ) { - PyErr_SetString( PyExc_ValueError, e.what() ); - throw boost::python::error_already_set(); - } - } - - - template<typename Field_T> - boost::python::object copyAdaptorToField( const Field_T & f ) - { - typedef GhostLayerField<typename Field_T::value_type, Field_T::F_SIZE> ResField; - auto res = make_shared< ResField > ( f.xSize(), f.ySize(), f.zSize(), f.nrOfGhostLayers() ); - - auto srcIt = f.beginWithGhostLayerXYZ(); - auto dstIt = res->beginWithGhostLayerXYZ(); - while ( srcIt != f.end() ) - { - for( cell_idx_t fCoord = 0; fCoord < cell_idx_c(Field_T::F_SIZE); ++fCoord ) - dstIt.getF( fCoord ) = srcIt.getF( fCoord ); - - ++srcIt; - ++dstIt; - } - return boost::python::object( res ); - } - - - - //=================================================================================================================== - // - // Field export - // - //=================================================================================================================== - - template<typename T> - void exportFlagFieldIfUnsigned( typename std::enable_if<std::is_unsigned<T>::value >::type* = 0 ) - { - using namespace boost::python; - - class_< FlagField<T> , - shared_ptr<FlagField<T> >, - bases<GhostLayerField<T,1> >, - boost::noncopyable > ( "FlagField", no_init ) - .def ( "registerFlag", &FF_registerFlag<T> , ( arg("flagName"), arg("bitNr") = object() ) ) - .def ( "flag", &FF_getFlag<T> ) - .def ( "flagName", &FF_getFlagName<T> ) - .add_property( "flags", &FF_registeredFlags<T> ) - .add_property( "flagMap", &FF_flagMap<T> ) - ; - - } - template<typename T> - void exportFlagFieldIfUnsigned( typename std::enable_if< ! std::is_unsigned<T>::value >::type* = 0 ) {} - - - struct FieldExporter - { - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - typedef typename FieldType::value_type T; - const uint_t F_SIZE = FieldType::F_SIZE; - typedef GhostLayerField<T,F_SIZE> GlField_T; - typedef Field<T,F_SIZE> Field_T; - - using namespace boost::python; - - class_<Field_T, shared_ptr<Field_T>, boost::noncopyable>( "Field", no_init ) - .add_property("layout", &field_layout < Field_T > ) - .add_property("size", &field_size < Field_T > ) - .add_property("allocSize", &field_allocSize < Field_T > ) - .add_property("strides", &field_strides < Field_T > ) - .add_property("offsets", &field_offsets < Field_T > ) - .def("clone", &Field_T::clone , return_value_policy<manage_new_object>()) - .def("cloneUninitialized", &Field_T::cloneUninitialized, return_value_policy<manage_new_object>()) - .def("swapDataPointers", &field_swapDataPointers< Field_T > ) - .def("__getitem__", &field_getCellXYZ < Field_T > ) - .def("__setitem__", &field_setCellXYZ < Field_T > ) - .def("buffer", &field_getBufferInterface<T,F_SIZE>, ( arg("withGhostLayers") = false ) ); - ; - - class_< GlField_T , shared_ptr<GlField_T>, bases<Field_T>, boost::noncopyable > ( "GhostLayerField", no_init ) - .add_property("sizeWithGhostLayer", &field_sizeWithGhostLayer<GlField_T> ) - .add_property("nrOfGhostLayers", &GlField_T::nrOfGhostLayers ) - ; - - if ( F_SIZE == 1 ) - exportFlagFieldIfUnsigned<T>(); - - - // Field Buffer - FieldBufferType<T,F_SIZE>::value.tp_new = PyType_GenericNew; - if ( PyType_Ready(& FieldBufferType<T,F_SIZE>::value ) < 0 ) - return; - - Py_INCREF( (& FieldBufferType<T,F_SIZE>::value ) ); - PyModule_AddObject( boost::python::scope().ptr(), "FieldBuffer", (PyObject *)&FieldBufferType<T,F_SIZE>::value ); - - // Field Buffer with ghost layer - FieldBufferTypeGl<T,F_SIZE>::value.tp_new = PyType_GenericNew; - if ( PyType_Ready(& FieldBufferTypeGl<T,F_SIZE>::value ) < 0 ) - return; - - Py_INCREF( (& FieldBufferTypeGl<T,F_SIZE>::value ) ); - PyModule_AddObject( boost::python::scope().ptr(), "FieldBufferGl", (PyObject *)&FieldBufferTypeGl<T,F_SIZE>::value ); - - - // Field Buffer type - - if ( python_coupling::isTypeRegisteredInBoostPython< FieldAllocator<T> >() == false ) - { - class_< FieldAllocator<T>, shared_ptr<FieldAllocator<T> >, boost::noncopyable> ( "FieldAllocator", no_init ) - .def( "incrementReferenceCount", &FieldAllocator<T>::incrementReferenceCount ) - .def( "decrementReferenceCount", &FieldAllocator<T>::decrementReferenceCount ); - } - - using field::communication::PackInfo; - class_< PackInfo<GlField_T>, - shared_ptr< PackInfo<GlField_T> >, - bases<walberla::communication::UniformPackInfo>, - boost::noncopyable >( "FieldPackInfo", no_init ); - - - using field::communication::UniformMPIDatatypeInfo; - class_< UniformMPIDatatypeInfo<GlField_T>, - shared_ptr< UniformMPIDatatypeInfo<GlField_T> >, - bases<walberla::communication::UniformMPIDatatypeInfo>, - boost::noncopyable >( "FieldMPIDataTypeInfo", no_init ); - - } - }; - - - struct GhostLayerFieldAdaptorExporter - { - GhostLayerFieldAdaptorExporter( const std::string & name ) - : name_ ( name ) - {} - - template< typename Adaptor> - void operator()( python_coupling::NonCopyableWrap<Adaptor> ) - { - using namespace boost::python; - - class_< Adaptor, shared_ptr<Adaptor>, boost::noncopyable> ( name_.c_str(), no_init ) - .add_property("size", &field_size <Adaptor > ) - .add_property("sizeWithGhostLayer", &field_sizeWithGhostLayer<Adaptor> ) - .add_property("nrOfGhostLayers", &Adaptor::nrOfGhostLayers ) - .def("__getitem__", &field_getCellXYZ <Adaptor> ) - .def("copyToField", ©AdaptorToField <Adaptor> ); - } - - std::string name_; - }; - - //=================================================================================================================== - // - // createField - // - //=================================================================================================================== - - - class CreateFieldExporter - { - public: - CreateFieldExporter( uint_t xs, uint_t ys, uint_t zs, uint_t fs, uint_t gl, - Layout layout, const boost::python::object & type, uint_t alignment, - const shared_ptr<boost::python::object> & resultPointer ) - : xs_( xs ), ys_(ys), zs_(zs), fs_(fs), gl_(gl), - layout_( layout), type_( type ), alignment_(alignment), resultPointer_( resultPointer ) - {} - - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - using namespace boost::python; - typedef typename FieldType::value_type T; - const uint_t F_SIZE = FieldType::F_SIZE; - - if( F_SIZE != fs_ ) - return; - - if( python_coupling::isCppEqualToPythonType<T>( (PyTypeObject *)type_.ptr() ) ) - { - T initVal = T(); //extract<T> ( initValue_ ); - *resultPointer_ = object( make_shared< GhostLayerField<T,F_SIZE> >( xs_,ys_,zs_, gl_, initVal, layout_, - getAllocator<T>(alignment_))); - } - } - - private: - uint_t xs_; - uint_t ys_; - uint_t zs_; - uint_t fs_; - uint_t gl_; - Layout layout_; - boost::python::object type_; - uint_t alignment_; - shared_ptr<boost::python::object> resultPointer_; - }; - - template<typename FieldTypes> - boost::python::object createPythonField( boost::python::list size, - boost::python::object type, - uint_t ghostLayers, - Layout layout, - uint_t alignment) - { - using namespace boost::python; - uint_t xSize = extract<uint_t> ( size[0] ); - uint_t ySize = extract<uint_t> ( size[1] ); - uint_t zSize = extract<uint_t> ( size[2] ); - uint_t sizeLen = uint_c( len( size ) ); - uint_t fSize = 1; - if ( sizeLen == 4 ) - fSize = extract<uint_t> ( size[3] ); - - if ( ! PyType_Check( type.ptr() ) ) { - PyErr_SetString( PyExc_RuntimeError, "Invalid 'type' parameter"); - throw error_already_set(); - } - - auto result = make_shared<boost::python::object>(); - CreateFieldExporter exporter( xSize,ySize, zSize, fSize, ghostLayers, layout, type, alignment, result ); - python_coupling::for_each_noncopyable_type< FieldTypes > ( exporter ); - - if ( *result == object() ) - { - PyErr_SetString( PyExc_ValueError, "Cannot create field of this (type,f-size) combination"); - throw error_already_set(); - } - else { - return *result; - } - } - - //=================================================================================================================== - // - // createFlagField - // - //=================================================================================================================== - - - inline boost::python::object createPythonFlagField( boost::python::list size, uint_t nrOfBits, uint_t ghostLayers ) - { - using namespace boost::python; - - uint_t sizeLen = uint_c( len( size ) ); - if ( sizeLen != 3 ) { - PyErr_SetString( PyExc_ValueError, "Size parameter has to be a list of length 3"); - throw error_already_set(); - } - uint_t xSize = extract<uint_t> ( size[0] ); - uint_t ySize = extract<uint_t> ( size[1] ); - uint_t zSize = extract<uint_t> ( size[2] ); - - if( nrOfBits == 8 ) return object( make_shared< FlagField< uint8_t > >( xSize, ySize, zSize, ghostLayers ) ); - else if ( nrOfBits == 16 ) return object( make_shared< FlagField< uint16_t> >( xSize, ySize, zSize, ghostLayers ) ); - else if ( nrOfBits == 32 ) return object( make_shared< FlagField< uint32_t> >( xSize, ySize, zSize, ghostLayers ) ); - else if ( nrOfBits == 64 ) return object( make_shared< FlagField< uint64_t> >( xSize, ySize, zSize, ghostLayers ) ); - else - { - PyErr_SetString( PyExc_ValueError, "Allowed values for number of bits are: 8,16,32,64"); - throw error_already_set(); - } - } - - - //=================================================================================================================== - // - // addToStorage - // - //=================================================================================================================== - - class AddToStorageExporter - { - public: - AddToStorageExporter(const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, uint_t fs, uint_t gl, Layout layout, - const boost::python::object & type, - const boost::python::object & initObj, - uint_t alignment ) - : blocks_( blocks ), name_( name ), fs_( fs ), - gl_(gl),layout_( layout), type_( type ), initObj_( initObj), alignment_(alignment), found_(false) - {} - - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - using namespace boost::python; - typedef typename FieldType::value_type T; - const uint_t F_SIZE = FieldType::F_SIZE; - - if( F_SIZE != fs_ ) - return; - - if( !found_ && python_coupling::isCppEqualToPythonType<T>( (PyTypeObject *)type_.ptr() ) ) - { - typedef internal::GhostLayerFieldDataHandling< GhostLayerField<T,F_SIZE > > DataHandling; - if ( initObj_ == object() ) { - auto dataHandling = walberla::make_shared< DataHandling >( blocks_, gl_, T(), layout_, alignment_ ); - blocks_->addBlockData( dataHandling, name_ ); - } - else { - auto dataHandling = walberla::make_shared< DataHandling >( blocks_, gl_, extract<T>(initObj_), layout_, alignment_ ); - blocks_->addBlockData( dataHandling, name_ ); - } - found_ = true; - } - } - - bool successful() const { return found_; } - private: - shared_ptr< StructuredBlockStorage > blocks_; - std::string name_; - uint_t fs_; - uint_t gl_; - Layout layout_; - boost::python::object type_; - boost::python::object initObj_; - uint_t alignment_; - bool found_; - }; - - template<typename FieldTypes> - void addToStorage( const shared_ptr<StructuredBlockStorage> & blocks, const std::string & name, - boost::python::object type, uint_t fs, uint_t gl, Layout layout, boost::python::object initValue, - uint_t alignment) - { - using namespace boost::python; - - if ( ! PyType_Check( type.ptr() ) ) { - PyErr_SetString( PyExc_RuntimeError, "Invalid 'type' parameter"); - throw error_already_set(); - } - - auto result = make_shared<boost::python::object>(); - AddToStorageExporter exporter( blocks, name, fs, gl, layout, type, initValue, alignment ); - python_coupling::for_each_noncopyable_type< FieldTypes > ( std::ref(exporter) ); - - if ( ! exporter.successful() ) { - PyErr_SetString( PyExc_ValueError, "Adding Field failed."); - throw error_already_set(); - } - } - - - inline void addFlagFieldToStorage( const shared_ptr<StructuredBlockStorage> & blocks, const std::string & name, - uint_t nrOfBits, uint_t gl ) - { - if( nrOfBits == 8 ) field::addFlagFieldToStorage< FlagField<uint8_t> > ( blocks, name, gl ); - else if ( nrOfBits == 16 ) field::addFlagFieldToStorage< FlagField<uint16_t> > ( blocks, name, gl ); - else if ( nrOfBits == 32 ) field::addFlagFieldToStorage< FlagField<uint32_t> > ( blocks, name, gl ); - else if ( nrOfBits == 64 ) field::addFlagFieldToStorage< FlagField<uint64_t> > ( blocks, name, gl ); - else - { - PyErr_SetString( PyExc_ValueError, "Allowed values for number of bits are: 8,16,32,64"); - throw boost::python::error_already_set(); - } - } - - //=================================================================================================================== - // - // createVTKWriter - // - //=================================================================================================================== - - class CreateVTKWriterExporter - { - public: - CreateVTKWriterExporter( const shared_ptr<StructuredBlockStorage> & blocks, - ConstBlockDataID fieldId, const std::string & vtkName) - : blocks_( blocks ), fieldId_(fieldId), vtkName_( vtkName ) - {} - - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - using namespace boost::python; - - IBlock * firstBlock = & ( * blocks_->begin() ); - if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) - writer_ = shared_ptr<field::VTKWriter<FieldType> >( new field::VTKWriter<FieldType>(fieldId_, vtkName_)); - } - - shared_ptr< vtk::BlockCellDataWriterInterface > getCreatedWriter() { - return writer_; - } - - private: - shared_ptr< vtk::BlockCellDataWriterInterface > writer_; - shared_ptr< StructuredBlockStorage > blocks_; - ConstBlockDataID fieldId_; - std::string vtkName_; - }; - - - template<typename FieldTypes> - inline shared_ptr<vtk::BlockCellDataWriterInterface> createVTKWriter(const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, - const std::string & nameInVtkOutput = "") - { - std::string vtkName = nameInVtkOutput; - if( vtkName.size() == 0) - vtkName = name; - - if ( blocks->begin() == blocks->end() ) - return shared_ptr<vtk::BlockCellDataWriterInterface>(); - auto fieldID = python_coupling::blockDataIDFromString( *blocks, name ); - - CreateVTKWriterExporter exporter(blocks, fieldID, vtkName); - python_coupling::for_each_noncopyable_type< FieldTypes > ( std::ref(exporter) ); - if ( ! exporter.getCreatedWriter() ) { - PyErr_SetString( PyExc_ValueError, "Failed to create writer"); - throw boost::python::error_already_set(); - } - else { - return exporter.getCreatedWriter(); - } - } - - //=================================================================================================================== - // - // createFlagFieldVTKWriter - // - //=================================================================================================================== - - class CreateFlagFieldVTKWriterExporter - { - public: - CreateFlagFieldVTKWriterExporter( const shared_ptr<StructuredBlockStorage> & blocks, - ConstBlockDataID fieldId, const std::string & vtkName, - boost::python::dict flagMapping) - : blocks_( blocks ), fieldId_(fieldId), vtkName_( vtkName ), flagMapping_( flagMapping ) - {} - - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - using namespace boost::python; - - IBlock * firstBlock = & ( * blocks_->begin() ); - if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) - { - typedef typename FieldType::flag_t flag_t; - typedef field::FlagFieldMapping<FieldType, flag_t> FFMapping; - auto uncastedWriter = shared_ptr<FFMapping >( new FFMapping(fieldId_, vtkName_)); - writer_ = uncastedWriter; - auto keys = flagMapping_.keys(); - - for( int i=0; i < len(keys); ++i ) { - uncastedWriter->addMapping(FlagUID(extract<std::string>(keys[i])), - extract<flag_t>(flagMapping_[keys[i]]) ); - } - } - - } - - shared_ptr< vtk::BlockCellDataWriterInterface > getCreatedWriter() { - return writer_; - } - - private: - shared_ptr< vtk::BlockCellDataWriterInterface > writer_; - shared_ptr< StructuredBlockStorage > blocks_; - ConstBlockDataID fieldId_; - std::string vtkName_; - boost::python::dict flagMapping_; - }; - - - template<typename FieldTypes> - inline shared_ptr<vtk::BlockCellDataWriterInterface> createFlagFieldVTKWriter(const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, - boost::python::dict flagMapping, - const std::string & nameInVtkOutput = "" ) - { - std::string vtkName = nameInVtkOutput; - if( vtkName.size() == 0) - vtkName = name; - - if ( blocks->begin() == blocks->end() ) - return shared_ptr<vtk::BlockCellDataWriterInterface>(); - auto fieldID = python_coupling::blockDataIDFromString( *blocks, name ); - - CreateFlagFieldVTKWriterExporter exporter(blocks, fieldID, vtkName, flagMapping); - python_coupling::for_each_noncopyable_type< FieldTypes > ( std::ref(exporter) ); - if ( ! exporter.getCreatedWriter() ) { - PyErr_SetString( PyExc_ValueError, "Failed to create writer"); - throw boost::python::error_already_set(); - } - else { - return exporter.getCreatedWriter(); - } - } - - - //=================================================================================================================== - // - // createBinarizationFieldWriter - // - //=================================================================================================================== - - class CreateBinarizationVTKWriterExporter - { - public: - CreateBinarizationVTKWriterExporter( const shared_ptr<StructuredBlockStorage> & blocks, - ConstBlockDataID fieldId, const std::string & vtkName, uint_t mask ) - : blocks_( blocks ), fieldId_(fieldId), vtkName_( vtkName ), mask_(mask) - {} - - template< typename FieldType> - void operator() ( python_coupling::NonCopyableWrap<FieldType> ) - { - using namespace boost::python; - - IBlock * firstBlock = & ( * blocks_->begin() ); - if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) - { - typedef field::BinarizationFieldWriter<FieldType> Writer; - writer_ = shared_ptr<Writer>( new Writer(fieldId_, vtkName_, - static_cast<typename FieldType::value_type>(mask_) )); - } - } - - shared_ptr< vtk::BlockCellDataWriterInterface > getCreatedWriter() { - return writer_; - } - - private: - shared_ptr< vtk::BlockCellDataWriterInterface > writer_; - shared_ptr< StructuredBlockStorage > blocks_; - ConstBlockDataID fieldId_; - std::string vtkName_; - uint_t mask_; - }; - - - template<typename FieldTypes> - inline shared_ptr<vtk::BlockCellDataWriterInterface> createBinarizationVTKWriter(const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, - uint_t mask, - const std::string & nameInVtkOutput = "" ) - { - std::string vtkName = nameInVtkOutput; - if( vtkName.size() == 0) - vtkName = name; - - if ( blocks->begin() == blocks->end() ) - return shared_ptr<vtk::BlockCellDataWriterInterface>(); - auto fieldID = python_coupling::blockDataIDFromString( *blocks, name ); - - CreateBinarizationVTKWriterExporter exporter(blocks, fieldID, vtkName, mask); - python_coupling::for_each_noncopyable_type< FieldTypes > ( std::ref(exporter) ); - if ( ! exporter.getCreatedWriter() ) { - PyErr_SetString( PyExc_ValueError, "Failed to create writer"); - throw boost::python::error_already_set(); - } - else { - return exporter.getCreatedWriter(); - } - } - - -} // namespace internal - - - - -template<typename FieldTypes > -void exportFields() -{ - using namespace boost::python; - - enum_<Layout>("Layout") - .value("fzyx", fzyx) - .value("zyxf", zyxf) - .export_values(); - - python_coupling::for_each_noncopyable_type< FieldTypes > ( internal::FieldExporter() ); - - def( "createField", &internal::createPythonField<FieldTypes>, ( ( arg("size") ), - ( arg("type") ), - ( arg("ghostLayers") = uint_t(1) ), - ( arg("layout") = zyxf ), - ( arg("alignment") = 0 )) ); - - def( "createFlagField", &internal::createPythonFlagField, ( ( arg("size") ), - ( arg("nrOfBits") = uint_t(32) ), - ( arg("ghostLayers") = uint_t(1) ) ) ); - - def( "addToStorage", &internal::addToStorage<FieldTypes>, ( ( arg("blocks") ), - ( arg("name") ), - ( arg("type") ), - ( arg("fSize") = 1 ), - ( arg("ghostLayers") = uint_t(1) ), - ( arg("layout") = zyxf ), - ( arg("initValue") = object() ), - ( arg("alignment") = 0 ) ) ); - - def( "addFlagFieldToStorage",&internal::addFlagFieldToStorage, ( ( arg("blocks") ), - ( arg("name") ), - ( arg("nrOfBits")=8 ), - ( arg("ghostLayers") = uint_t(1) ) ) ); - - def( "createVTKWriter", &internal::createVTKWriter<FieldTypes>, ( arg("blocks"), arg("name"), arg("vtkName")="" )); - - - typedef boost::mpl::vector< - FlagField<uint8_t>, - FlagField<uint16_t>, - FlagField<uint32_t>, - FlagField<uint64_t> > FlagFields; - - def( "createFlagFieldVTKWriter", &internal::createFlagFieldVTKWriter<FlagFields>, - ( arg("blocks"), arg("name"), arg("flagMapping"), arg("vtkName")="" )); - - - typedef boost::mpl::vector< - Field<uint8_t,1 >, - Field<uint16_t, 1>, - Field<uint32_t, 1>, - Field<uint64_t, 1> > UintFields; - - def( "createBinarizationVTKWriter", &internal::createBinarizationVTKWriter<UintFields>, - ( arg("blocks"), arg("name"), arg("mask"), arg("vtkName")="" )); -} - - -template<typename AdaptorTypes> -void exportGhostLayerFieldAdaptors() -{ - python_coupling::for_each_noncopyable_type< AdaptorTypes > ( internal::GhostLayerFieldAdaptorExporter("FieldAdaptor") ); -} - -template<typename AdaptorType> -void exportGhostLayerFieldAdaptor() -{ - typedef boost::mpl::vector< AdaptorType > AdaptorTypes; - exportGhostLayerFieldAdaptors<AdaptorTypes>( ); -} - - - - -} // namespace field -} // namespace walberla - - diff --git a/src/field/python/GatherExport.h b/src/field/python/GatherExport.h deleted file mode 100644 index 1105caa00..000000000 --- a/src/field/python/GatherExport.h +++ /dev/null @@ -1,38 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file GatherExport.h -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - - -namespace walberla { -namespace field { - - -template<typename FieldTypes > -void exportGatherFunctions(); - - - -} // namespace field -} // namespace walberla - - -#include "GatherExport.impl.h" diff --git a/src/field/python/GatherExport.impl.h b/src/field/python/GatherExport.impl.h deleted file mode 100644 index 42f66565c..000000000 --- a/src/field/python/GatherExport.impl.h +++ /dev/null @@ -1,109 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file GatherExport.impl.h -//! \ingroup field -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "field/Gather.h" -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" -#include "python_coupling/helper/ModuleScope.h" -#include "python_coupling/helper/SliceToCellInterval.h" - - -namespace walberla { -namespace field { - - -namespace internal { - - //=================================================================================================================== - // - // Gather - // - //=================================================================================================================== - - - template<typename Field_T> - boost::python::object gatherToObject( const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID fieldID, - CellInterval boundingBox = CellInterval(), int targetRank = 0 ) - { - typedef Field< typename Field_T::value_type, Field_T::F_SIZE > ResultField; - auto result = make_shared< ResultField > ( 0,0,0 ); - field::gather< Field_T, ResultField > ( *result, blocks, fieldID, boundingBox, targetRank, MPI_COMM_WORLD ); - - if ( MPIManager::instance()->worldRank() == targetRank ) - return boost::python::object(result); - else - return boost::python::object(); - } - - FunctionExporterClass( gatherToObject, - boost::python::object( const shared_ptr<StructuredBlockStorage> &, - BlockDataID, CellInterval,int ) ); - - template<typename FieldTypes> - static boost::python::object gatherWrapper ( const shared_ptr<StructuredBlockStorage> & blocks, const std::string & blockDataStr, - const boost::python::tuple & slice, int targetRank = 0 ) - { - using namespace boost::python; - - auto fieldID = python_coupling::blockDataIDFromString( *blocks, blockDataStr ); - CellInterval boundingBox = python_coupling::globalPythonSliceToCellInterval( blocks, slice ); - - if ( blocks->begin() == blocks->end() ) { - // if no blocks are on this process the field::gather function can be called with any type - // however we have to call it, otherwise a deadlock occurs - gatherToObject< Field<real_t,1> > ( blocks, fieldID, boundingBox, targetRank ); - return object(); - } - - IBlock * firstBlock = & ( * blocks->begin() ); - python_coupling::Dispatcher<FieldTypes, Exporter_gatherToObject > dispatcher( firstBlock ); - auto func = dispatcher( fieldID ); - if ( !func ) - { - PyErr_SetString( PyExc_RuntimeError, "This function cannot handle this type of block data."); - throw error_already_set(); - } - else - { - return func( blocks, fieldID, boundingBox, targetRank) ; - } - } - -} // namespace internal - - - -template<typename FieldTypes > -void exportGatherFunctions() -{ - using namespace boost::python; - python_coupling::ModuleScope fieldModule( "field" ); - - def( "gather", &internal::gatherWrapper<FieldTypes>, ( arg("blocks"), arg("blockDataName"), arg("slice"), arg("targetRank") = 0 ) ); -} - - - - -} // namespace moduleName -} // namespace walberla - - diff --git a/src/geometry/python/Exports.cpp b/src/geometry/python/Exports.cpp deleted file mode 100644 index aa9f32f7b..000000000 --- a/src/geometry/python/Exports.cpp +++ /dev/null @@ -1,103 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.cpp -//! \ingroup geometry -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/helper/ModuleScope.h" - -#include "core/math/AABB.h" -#include "geometry/mesh/TriangleMesh.h" -#include "geometry/mesh/TriangleMeshIO.h" - - - -using namespace boost::python; - - -namespace walberla { -namespace geometry { - -void triangleMesh_scaleReal( TriangleMesh & m, real_t scaleFactor ) { m.scale( scaleFactor ); } -void triangleMesh_scaleVec ( TriangleMesh & m, const Vector3<real_t> & scaleVec ) { m.scale( scaleVec ); } - - -shared_ptr<TriangleMesh> triangleMesh_load ( const std::string & filename, bool broadcast ) -{ - auto mesh = make_shared<TriangleMesh>(); - - try - { - if( broadcast ) - readAndBroadcastMesh( filename, *mesh ); - else - readMesh( filename, *mesh ); - } - catch( std::exception & e ) - { - PyErr_SetString( PyExc_RuntimeError, e.what() ); - throw error_already_set(); - } - return mesh; -} - -void triangleMesh_save( TriangleMesh & mesh, const std::string & filename ) -{ - try - { - writeMesh( filename, mesh ); - } - catch( std::exception & e ) - { - PyErr_SetString( PyExc_RuntimeError, e.what() ); - throw error_already_set(); - } -} - - - -void exportModuleToPython() -{ - python_coupling::ModuleScope fieldModule( "geometry" ); - - class_< TriangleMesh, shared_ptr<TriangleMesh>, boost::noncopyable > ( "TriangleMesh", no_init ) - .add_property( "numTriangles", &TriangleMesh::getNumTriangles ) - .add_property( "numVertices" , &TriangleMesh::getNumVertices ) - .add_property( "numVertexNormals", &TriangleMesh::getNumNormals ) - .def ( "volume", &TriangleMesh::volume ) - .def ( "scale", &triangleMesh_scaleReal, arg("factor") ) - .def ( "exchangeAxes", &TriangleMesh::exchangeAxes, ( arg("xAxisId"), arg("yAxisId"), arg("zAxisId") ) ) - .def ( "scaleXYZ", &triangleMesh_scaleVec, arg("factors") ) - .def ( "removeDuplicateVertices", &TriangleMesh::removeDuplicateVertices, ( arg("tolerance") = 1e-4 ) ) - .def ( "merge", &TriangleMesh::merge, ( arg("other"), arg("offset") = Vector3<real_t>(0) ) ) - .def ( "getAABB", &TriangleMesh::getAABB ) - .def ( "save", &triangleMesh_save, arg("filename") ) - .def ( "load", &triangleMesh_load, ( arg("filename"), arg("broadcast") = true) ).staticmethod( "load" ) - ; -} - - -} // namespace geometry -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/geometry/python/Exports.h b/src/geometry/python/Exports.h deleted file mode 100644 index 5b92ae1da..000000000 --- a/src/geometry/python/Exports.h +++ /dev/null @@ -1,38 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.h -//! \ingroup geometry -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace geometry { - - - void exportModuleToPython(); - - -} // namespace geometry -} // namespace walberla - - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/lbm/python/ExportBasic.cpp b/src/lbm/python/ExportBasic.cpp deleted file mode 100644 index b8af2324a..000000000 --- a/src/lbm/python/ExportBasic.cpp +++ /dev/null @@ -1,244 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.cpp -//! \ingroup domain_decomposition -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "ExportBasic.h" -#include "core/logging/Logging.h" -#include "python_coupling/Manager.h" -#include "python_coupling/helper/ModuleScope.h" - -#include "domain_decomposition/BlockDataID.h" - -#include "lbm/field/Adaptors.h" -#include "lbm/lattice_model/CollisionModel.h" -#include "lbm/lattice_model/D3Q19.h" -#include "lbm/lattice_model/ForceModel.h" - - -using namespace boost::python; - - -namespace walberla { -namespace lbm { - - -namespace internal { - - using namespace boost::python; - - - template<typename ForceModel_T> bool FM_getShiftMacVel( const ForceModel_T & ) { return ForceModel_T::shiftMacVel; } - template<typename ForceModel_T> bool FM_getShiftEquVel( const ForceModel_T & ) { return ForceModel_T::shiftEquVel; } - template<typename ForceModel_T> bool FM_getConstant ( const ForceModel_T & ) { return ForceModel_T::constant; } - - template<typename ForceModel_T> - void addForceModelDefs( class_< ForceModel_T> & c ) - { - c.add_property( "shiftMacVel", FM_getShiftMacVel<ForceModel_T> ) - .add_property( "shiftEquVel", FM_getShiftEquVel<ForceModel_T> ) - .add_property( "constant", FM_getConstant <ForceModel_T>) - .def( "setConstantBodyForceIfPossible", &ForceModel_T::setConstantBodyForceIfPossible ) - ; - } - - - list getMRTRelaxationRates( const collision_model::D3Q19MRT & l ) - { - list result; - for(uint_t i=0; i<19; ++i ) - result.append( l.s(i) ); - return result; - } - - list getCumulantRelaxationRates( const collision_model::D3Q27Cumulant & l ) - { - list result; - for(uint_t i=0; i<10; ++i ) - result.append( l.omega(i) ); - return result; - } -} - -void exportForceModels() -{ - static bool alreadyExported = false; - if( alreadyExported ) - return; - alreadyExported = true; - - using namespace internal; - using namespace force_model; - - python_coupling::ModuleScope scope("forceModels"); - - typedef GhostLayerField<Vector3<real_t>,1 > VecField; - - auto export1 = class_< None >("NoForce" ); // "None" is a Python keyword -> renamed to NoForce - addForceModelDefs( export1 ); - - auto export2 = class_< SimpleConstant >("SimpleConstant", - init<Vector3<real_t> , uint_t> ( (arg("force"), arg("level")=uint_t(0) ) ) ); - const Vector3<real_t>& (SimpleConstant::*p_SimpleConstantForce)() const = &SimpleConstant::force; - export2.def("force", p_SimpleConstantForce, return_value_policy<copy_const_reference>()); - addForceModelDefs( export2 ); - - auto export3 = class_< EDMField<VecField> > ("EDMField", - init< BlockDataID> ( (arg("forceFieldID") )) ); - addForceModelDefs( export3 ); - - auto export4 = class_< LuoConstant > ("LuoConstant", - init<Vector3<real_t> , uint_t> ( (arg("force"), arg("level")=uint_t(0) ) ) ); - const Vector3<real_t>& (LuoConstant::*p_LuoConstantForce)() const = &LuoConstant::force; - export4.def("force", p_LuoConstantForce, return_value_policy<copy_const_reference>() ); - addForceModelDefs( export4 ); - - auto export5 = class_< LuoField<VecField> > ("LuoField", - init< BlockDataID> ( (arg("forceFieldID") )) ); - addForceModelDefs( export5 ); - - auto export6 = class_< GuoConstant > ("GuoConstant", - init<Vector3<real_t> , uint_t> ( (arg("force"), arg("level")=uint_t(0) ) ) ); - const Vector3<real_t>& (GuoConstant::*p_GuoConstantForce)() const = &GuoConstant::force; - export6.def("force", p_GuoConstantForce, return_value_policy<copy_const_reference>() ); - addForceModelDefs( export6 ); - - auto export7 = class_< GuoField<VecField> > ("GuoField", - init< BlockDataID> ( (arg("forceFieldID") )) ); - addForceModelDefs( export7 ); - - auto export8 = class_< Correction<VecField> > ("Correction", - init< BlockDataID> ( (arg("previousMomentumDensityFieldID") )) ); - addForceModelDefs( export8 ); -} - - - -shared_ptr< collision_model::SRTField<GhostLayerField<real_t,1> > > createSRTFieldLatticeModel( const shared_ptr<StructuredBlockStorage> & bs, const std::string & blockDataName, uint_t level ) -{ - auto blockDataID = python_coupling::blockDataIDFromString( *bs, blockDataName ); - return make_shared< collision_model::SRTField<GhostLayerField<real_t,1> > >( blockDataID, level ); -} - -void exportCollisionModels() -{ - static bool alreadyExported = false; - if( alreadyExported ) - return; - alreadyExported = true; - - using namespace internal; - - python_coupling::ModuleScope scope("collisionModels"); - - using collision_model::SRT; - using collision_model::SRTField; - using collision_model::TRT; - using collision_model::D3Q19MRT; - using collision_model::D3Q27Cumulant; - - def( "levelDependentRelaxationParameter", collision_model::levelDependentRelaxationParameter, (arg("targetLevel"), arg("parameterLevel"), arg("level")) ); - def( "viscosityFromOmega", collision_model::viscosityFromOmega, (arg("omega") ) ); - def( "omegaFromViscosity", collision_model::omegaFromViscosity, (arg("viscosity") ) ); - - // SRT - { - real_t ( SRT::*ptr_omega )() const = &SRT::omega; - real_t ( SRT::*ptr_viscosity )() const = &SRT::viscosity; - - class_< SRT > ("SRT", init<real_t, uint_t>( ( arg("omega"), arg("level")=uint_t(0) ) ) ) - .add_property("omega", ptr_omega) - .add_property("viscosity", ptr_viscosity ) - .add_property("level", &SRT::level ) - .def("reset", &SRT::reset, (arg("omega"), arg("level")=uint_t(0) ) ) - ; - } - - // SRTField - { - typedef SRTField<GhostLayerField<real_t,1> > SRTField_T; - class_< SRTField_T >( "SRTField", no_init ) - .def( "__init__", make_constructor( &createSRTFieldLatticeModel) ) - ; - } - - // TRT - { - real_t ( TRT::*ptr_lambda_e )() const = &TRT::lambda_e; - real_t ( TRT::*ptr_lambda_d )() const = &TRT::lambda_d; - real_t ( TRT::*ptr_viscosity )() const = &TRT::viscosity; - - class_< TRT> ( "TRT", init<real_t, real_t, uint_t>( (arg("lambda_e"), arg("lambda_d"), arg("level")=uint_t(0) ) ) ) - .add_property("lambda_e", ptr_lambda_e ) - .add_property("lambda_d", ptr_lambda_d ) - .add_property("lambda_o", ptr_lambda_d ) - .add_property("viscosity", ptr_viscosity ) - .add_property("level", &TRT::level ) - .def( "reset", &TRT::reset, ( arg("lambda_e"), arg("lambda_d"), arg("level")=uint_t(0) ) ) - .def( "resetWithMagicNumber", &TRT::resetWithMagicNumber, (arg("omega"), arg("magicNumber") = TRT::threeSixteenth,arg("level")=uint_t(0) ) ) - .def( "constructWithMagicNumber", &TRT::constructWithMagicNumber, (arg("omega"), arg("magicNumber") = TRT::threeSixteenth, arg("level")=uint_t(0) ) ) - .staticmethod("constructWithMagicNumber") - ; - } - - // MRT - { - real_t ( D3Q19MRT::*ptr_viscosity )() const = &D3Q19MRT::viscosity; - - class_< D3Q19MRT >( "D3Q19MRT", init<real_t, real_t, real_t, real_t, real_t, real_t, uint_t>( - ( arg("s1"), arg("s2"), arg("s4"), arg("s9"), arg("s10"), arg("s16"), arg("level")=uint_t(0) ) )) - .add_property( "relaxationRates", getMRTRelaxationRates ) - .add_property( "viscosity", ptr_viscosity ) - .def( "constructTRT", &D3Q19MRT::constructTRT, (arg("lambda_e"), arg("lambda_d"), arg("level")=uint_t(0) ) ) - .staticmethod("constructTRT") - .def( "constructTRTWithMagicNumber", &D3Q19MRT::constructTRTWithMagicNumber, (arg("omega"), arg("magicNumber")=D3Q19MRT::threeSixteenth, arg("level")=uint_t(0) ) ) - .staticmethod("constructTRTWithMagicNumber") - .def( "constructPan", &D3Q19MRT::constructPan, (arg("lambda_e"), arg("lambda_d"), arg("level")=uint_t(0) ) ) - .staticmethod("constructPan") - .def( "constructPanWithMagicNumber", &D3Q19MRT::constructPanWithMagicNumber, (arg("omega"), arg("magicNumber")=D3Q19MRT::threeSixteenth, arg("level")=uint_t(0) ) ) - .staticmethod("constructPanWithMagicNumber") - ; - } - - // Cumulant - { - real_t ( D3Q27Cumulant::*ptr_viscosity )() const = &D3Q27Cumulant::viscosity; - - class_< D3Q27Cumulant >( "D3Q27Cumulant", init<real_t, real_t, real_t, real_t, real_t, real_t, real_t, real_t, real_t,real_t, uint_t>( - ( arg("omega1"), arg("omega2")=real_t(1), arg("omega3")=real_t(1), arg("omega4")=real_t(1), arg("omega5")=real_t(1), arg("omega6")=real_t(1), arg("omega7")=real_t(1), arg("omega8")=real_t(1), arg("omega9")=real_t(1), arg("omega10")=real_t(1), arg("level")=uint_t(0) ) )) - .add_property( "relaxationRates", getCumulantRelaxationRates ) - .add_property( "viscosity", ptr_viscosity ) - ; - - } - -} - - - -} // namespace lbm -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/lbm/python/ExportBasic.h b/src/lbm/python/ExportBasic.h deleted file mode 100644 index ba55ed144..000000000 --- a/src/lbm/python/ExportBasic.h +++ /dev/null @@ -1,47 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportBasic.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace lbm { - - void exportCollisionModels(); - void exportForceModels(); - - template< typename LatticeModels > struct VelocityAdaptorsFromLatticeModels; - template< typename LatticeModels > struct DensityAdaptorsFromLatticeModels; - template< typename LatticeModels > struct AdaptorsFromLatticeModels; - template< typename LatticeModels > struct ExtendedBoundaryHandlingsFromLatticeModels; - - template<typename LatticeModels, typename FlagFields> - void exportBasic(); - -} // namespace lbm -} // namespace walberla - - -#include "ExportBasic.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/lbm/python/ExportBasic.impl.h b/src/lbm/python/ExportBasic.impl.h deleted file mode 100644 index c0097f54e..000000000 --- a/src/lbm/python/ExportBasic.impl.h +++ /dev/null @@ -1,520 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportBasic.impl.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" -#include "python_coupling/helper/ModuleScope.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" - -#include "core/logging/Logging.h" - -#include "boundary/python/Exports.h" -#include "field/adaptors/AdaptorCreators.h" -#include "lbm/lattice_model/CollisionModel.h" -#include "lbm/lattice_model/ForceModel.h" -#include "lbm/sweeps/CellwiseSweep.h" -#include "lbm/field/AddToStorage.h" -#include "lbm/field/Adaptors.h" -#include "lbm/lattice_model/D3Q19.h" -#include "lbm/sweeps/SplitPureSweep.h" - -#include "field/python/FieldExport.h" - -#include <boost/mpl/transform.hpp> -#include <boost/mpl/copy.hpp> - - -namespace walberla { -namespace lbm { - - -namespace internal -{ - //=================================================================================================================== - // - // LatticeModel - // - //=================================================================================================================== - - class LatticeModelCreator - { - public: - LatticeModelCreator( bool compressible, uint_t eqOrder, const std::string & stencil, - boost::python::object collisionModel, boost::python::object forceModel ) - : compressible_( compressible ), equilibriumAccuracyOrder_( eqOrder ), stencil_( stencil ), - collisionModel_( collisionModel ), forceModel_( forceModel ) - {} - - template<typename LatticeModel_T> - void operator() ( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - using namespace boost::python; - using namespace collision_model; - - typedef typename LatticeModel_T::Stencil Stencil_T; - - if ( compressible_ != LatticeModel_T::compressible ) return; - if ( equilibriumAccuracyOrder_ != LatticeModel_T::equilibriumAccuracyOrder ) return; - if ( stencil_ != Stencil_T::NAME ) return; - - if ( ! extract< typename LatticeModel_T::CollisionModel>(collisionModel_).check() ) return; - if ( ! extract< typename LatticeModel_T::ForceModel >(forceModel_ ).check() ) return; - - result_ = object( LatticeModel_T( extract< typename LatticeModel_T::CollisionModel>(collisionModel_), - extract< typename LatticeModel_T::ForceModel >(forceModel_ ) ) ); - } - - boost::python::object getResult() const { return result_; } - - private: - bool compressible_; - uint_t equilibriumAccuracyOrder_; - std::string stencil_; - boost::python::object collisionModel_; - boost::python::object forceModel_; - - - boost::python::object result_; - }; - - struct LatticeModelExporter - { - template<typename LatticeModel_T> - static boost::python::list getDirections( LatticeModel_T & ) - { - using namespace boost::python; - list directionList; - const int dimension = LatticeModel_T::Stencil::D; - if( dimension < 1 || dimension > 3) { - PyErr_SetString( PyExc_ValueError, "Only stencils with dimensions 1,2,3 supported"); - throw error_already_set(); - } - for( auto it = LatticeModel_T::Stencil::begin(); it != LatticeModel_T::Stencil::end(); ++it ) - { - if(dimension == 1) { - directionList.append( make_tuple(it.cx() ) ); - } - else if( dimension == 2) { - directionList.append( make_tuple(it.cx(), it.cy()) ); - } else if (dimension == 3) { - directionList.append( make_tuple(it.cx(), it.cy(), it.cz() ) ); - } - } - return directionList; - } - template<typename LatticeModel_T> - static bool isCompressible(LatticeModel_T &) { - return LatticeModel_T::compressible; - } - template<typename LatticeModel_T> - static int equilibriumAccuracyOrder(LatticeModel_T &) { - return LatticeModel_T::equilibriumAccuracyOrder; - } - template<typename LatticeModel_T> - static boost::python::str stencilName(LatticeModel_T &) { - return boost::python::str(LatticeModel_T::Stencil::NAME); - } - template<typename LatticeModel_T> - static boost::python::str communicationStencilName(LatticeModel_T &) { - return boost::python::str(LatticeModel_T::CommunicationStencil::NAME); - } - - template<typename LatticeModel_T> - void operator()( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - using namespace boost::python; - - typename LatticeModel_T::CollisionModel& ( LatticeModel_T::*p_collisionModel) () = &LatticeModel_T::collisionModel; - typename LatticeModel_T::ForceModel & ( LatticeModel_T::*p_forceModel) () = &LatticeModel_T::forceModel; - - class_< LatticeModel_T > ( LatticeModel_T::NAME, no_init ) - .add_property( "collisionModel", make_function( p_collisionModel, return_internal_reference<>() ) ) - .add_property( "forceModel", make_function( p_forceModel , return_internal_reference<>() ) ) - .add_property( "compressible", &LatticeModelExporter::isCompressible<LatticeModel_T>) - .add_property( "equilibriumAccuracyOrder",&LatticeModelExporter::equilibriumAccuracyOrder<LatticeModel_T>) - .add_property( "stencilName", &LatticeModelExporter::stencilName<LatticeModel_T>) - .add_property( "communicationStencilName",&LatticeModelExporter::communicationStencilName<LatticeModel_T>) - .add_property( "directions", &LatticeModelExporter::getDirections<LatticeModel_T> ) - ; - } - }; - - - template<typename LatticeModels> - boost::python::object makeLatticeModel( const std::string & stencil, boost::python::object collisionModel, boost::python::object forceModel, - bool compressible, uint_t equilibriumAccuracyOrder ) - { - using namespace boost::python; - - LatticeModelCreator creator( compressible, equilibriumAccuracyOrder, stencil, collisionModel, forceModel ); - python_coupling::for_each_noncopyable_type<LatticeModels>( std::ref(creator) ); - - if ( creator.getResult() == object() ) - { - PyErr_SetString( PyExc_ValueError, "No such LatticeModel available."); - throw error_already_set(); - } - - return creator.getResult(); - } - - //=================================================================================================================== - // - // PdfField - // - //=================================================================================================================== - - template<typename LatticeModel_T> - typename PdfField<LatticeModel_T>::iterator pythonSliceToFieldIterator( PdfField<LatticeModel_T> & field, - boost::python::tuple pyIndex ) - { - using python_coupling::localPythonSliceToCellInterval; - return field.beginSliceXYZ( localPythonSliceToCellInterval(field, pyIndex) ); - } - - template<typename LatticeModel_T> - void pdfField_setDensityAndVelocity( PdfField<LatticeModel_T> & field, boost::python::tuple pyIndex, - const Vector3<real_t> & velocity, real_t rho ) - { - typename PdfField<LatticeModel_T>::iterator beginIterator = pythonSliceToFieldIterator<LatticeModel_T>( field, pyIndex ); - DensityAndVelocityRange< LatticeModel_T, typename PdfField<LatticeModel_T>::iterator >::set( beginIterator, field.end(), field.latticeModel(), velocity, rho ); - } - - template<typename LatticeModel_T> - void pdfField_setToEquilibrium( PdfField<LatticeModel_T> & field, boost::python::tuple pyIndex, - const Vector3<real_t> & velocity, real_t rho ) - { - typename PdfField<LatticeModel_T>::iterator beginIterator = pythonSliceToFieldIterator<LatticeModel_T>( field, pyIndex ); - EquilibriumRange< LatticeModel_T, typename PdfField<LatticeModel_T>::iterator >::set( beginIterator, field.end(), velocity, rho ); - } - - template<typename LatticeModel_T> - boost::python::list pdfField_getPressureTensor( PdfField<LatticeModel_T> & field, cell_idx_t x, cell_idx_t y, cell_idx_t z ) - { - using namespace boost::python; - - Matrix3<real_t> m = field.getPressureTensor( x,y,z ); - list result; - for(uint_t i=0; i<3; ++i ) - { - list row; - for(uint_t j=0; j<3; ++j) - row.append( m(i,j) ); - result.append(row); - } - return result; - } - - - struct PdfFieldExporter - { - template<typename LatticeModel_T> - void operator()( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - using namespace boost::python; - - typedef PdfField<LatticeModel_T> PdfField_T; - typedef GhostLayerField<real_t, LatticeModel_T::Stencil::Size > Base; - - - LatticeModel_T & ( PdfField_T::*ptr_latticeModel ) () = &PdfField_T::latticeModel; - - //real_t ( PdfField_T::*ptr_getShearRate )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getShearRate; - - real_t ( PdfField_T::*ptr_getDensity )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getDensity; - real_t ( PdfField_T::*ptr_getDensitySI )( cell_idx_t, cell_idx_t, cell_idx_t, real_t ) const = &PdfField_T::getDensitySI; - - Vector3<real_t> ( PdfField_T::*ptr_getMomentumDensity )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getMomentumDensity; - Vector3<real_t> ( PdfField_T::*ptr_getEquilibriumMomentumDensity )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getEquilibriumMomentumDensity; - real_t ( PdfField_T::*ptr_getShearRate )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getShearRate; - Vector3<real_t> ( PdfField_T::*ptr_getVelocity )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getVelocity; - Vector3<real_t> ( PdfField_T::*ptr_getVelocitySI )( cell_idx_t, cell_idx_t, cell_idx_t, real_t, real_t ) const = &PdfField_T::getVelocitySI; - Vector3<real_t> ( PdfField_T::*ptr_getEquilibriumVelocity )( cell_idx_t, cell_idx_t, cell_idx_t ) const = &PdfField_T::getEquilibriumVelocity; - - try - { - class_< PdfField_T, shared_ptr<PdfField_T>, bases< Base >, boost::noncopyable >("PdfField", no_init) - .add_property("latticeModel", make_function( ptr_latticeModel, return_internal_reference<>() ) ) - .def( "setDensityAndVelocity", pdfField_setDensityAndVelocity<LatticeModel_T>, ( args("slice"), args("velocity"), args("density") ) ) - .def( "setToEquilibrium", pdfField_setToEquilibrium<LatticeModel_T>, ( args("slice"), args("velocity"), args("density") ) ) - .def( "getShearRate", ptr_getShearRate , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getDensity", ptr_getDensity , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getDensitySI", ptr_getDensitySI , ( arg("x"), arg("y"), arg("z"), arg("rho_SI") ) ) - .def( "getMomentumDensity" , ptr_getMomentumDensity , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getEquilibriumMomentumDensity" , ptr_getEquilibriumMomentumDensity , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getVelocity" , ptr_getVelocity , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getVelocitySI" , ptr_getVelocitySI , ( arg("x"), arg("y"), arg("z"), arg("dx_SI"), arg("dy_SI") ) ) - .def( "getEquilibriumVelocity" , ptr_getEquilibriumVelocity , ( arg("x"), arg("y"), arg("z") ) ) - .def( "getPressureTensor", pdfField_getPressureTensor<LatticeModel_T> , ( arg("x"), arg("y"), arg("z") ) ) - ; - } - catch (...) { - WALBERLA_LOG_WARNING( "Exporting PDFField failed. Did you forget to export the corresponding GhostLayerField?" ); - } - } - }; - - class AddPdfFieldToStorageExporter - { - public: - AddPdfFieldToStorageExporter( const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, - boost::python::object latticeModel, - const Vector3<real_t> & initialVelocity, real_t initialDensity, - uint_t gl, field::Layout layout, - const std::string & densityAdaptorName, const std::string & velocityAdaptorName, - const std::string & shearRateAdaptorName ) - : blocks_( blocks ), name_( name ), latticeModel_(latticeModel), - initialVelocity_( initialVelocity ), initialDensity_( initialDensity ), - gl_(gl),layout_( layout), - densityAdaptorName_( densityAdaptorName ), velocityAdaptorName_( velocityAdaptorName ), - shearRateAdaptorName_( shearRateAdaptorName), - found_(false) - {} - - template< typename LatticeModel_T> - void operator() ( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - using namespace boost::python; - - if( ! extract<LatticeModel_T>( latticeModel_ ).check() ) - return; - - WALBERLA_ASSERT( !found_ ); - BlockDataID pdfFieldID = addPdfFieldToStorage<LatticeModel_T>( blocks_, name_, extract<LatticeModel_T>( latticeModel_ ), - initialVelocity_, initialDensity_, gl_, layout_ ); - - if ( densityAdaptorName_.size() > 0 ) { - field::addFieldAdaptor< typename lbm::Adaptor<LatticeModel_T>::Density > ( blocks_, pdfFieldID, densityAdaptorName_ ); - } - if ( velocityAdaptorName_.size() > 0 ) { - field::addFieldAdaptor< typename lbm::Adaptor<LatticeModel_T>::Velocity >( blocks_, pdfFieldID, velocityAdaptorName_ ); - } - if ( shearRateAdaptorName_.size() > 0 ) { - field::addFieldAdaptor< typename lbm::Adaptor<LatticeModel_T>::ShearRate >( blocks_, pdfFieldID, shearRateAdaptorName_ ); - } - found_ = true; - } - - bool successful() { return found_; } - - private: - shared_ptr< StructuredBlockStorage > blocks_; - std::string name_; - boost::python::object latticeModel_; - Vector3<real_t> initialVelocity_; - - real_t initialDensity_; - uint_t gl_; - field::Layout layout_; - - std::string densityAdaptorName_; - std::string velocityAdaptorName_; - std::string shearRateAdaptorName_; - - bool found_; - }; - - template<typename LatticeModels> - void addPdfFieldToStorage( const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & identifier, - boost::python::object latticeModel, - const Vector3<real_t> & initialVelocity, real_t initialDensity, - uint_t gl, field::Layout layout, - const std::string & densityAdaptor, const std::string & velocityAdaptor, - const std::string & shearRateAdaptor ) - { - using namespace boost::python; - - internal::AddPdfFieldToStorageExporter exporter( blocks, identifier, latticeModel, initialVelocity, - initialDensity, gl, layout, densityAdaptor, velocityAdaptor, shearRateAdaptor ); - python_coupling::for_each_noncopyable_type< LatticeModels > ( std::ref(exporter) ); - if ( ! exporter.successful() ) { - PyErr_SetString( PyExc_ValueError, "Adding Pdf Field failed."); - throw error_already_set(); - } - } - - struct AdaptorExporter - { - template< typename LatticeModel_T> - void operator() ( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - field::exportGhostLayerFieldAdaptor< typename Adaptor< LatticeModel_T >::Density > ( ); - field::exportGhostLayerFieldAdaptor< typename Adaptor< LatticeModel_T >::Velocity > ( ); - field::exportGhostLayerFieldAdaptor< typename Adaptor< LatticeModel_T >::ShearRate > ( ); - } - }; - - template< typename LatticeModel > - struct VelocityAdaptorFromLatticeModel - { - typedef typename lbm::Adaptor< LatticeModel>::Velocity type; - }; - - template< typename LatticeModel > - struct DensityAdaptorFromLatticeModel - { - typedef typename lbm::Adaptor< LatticeModel>::Density type; - }; - - template< typename LatticeModel > - struct ShearRateAdaptorFromLatticeModel - { - typedef typename lbm::Adaptor< LatticeModel>::ShearRate type; - }; - - class SweepWrapper - { - public: - SweepWrapper() - {} - - SweepWrapper( const std::function<void(IBlock*) > & sweepToWrap ) - : sweepToWrap_( sweepToWrap ) {} - - void operator() ( IBlock * block ) - { - if ( sweepToWrap_ ) - sweepToWrap_( block ); - } - protected: - std::function<void(IBlock*) > sweepToWrap_; - }; - - - inline shared_ptr<SweepWrapper> makeSplitPureSweep(const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & pdfFieldIDStr) - { - if ( blocks->begin() == blocks->end() ) { - PyErr_SetString( PyExc_RuntimeError, "No blocks on this process" ); - throw boost::python::error_already_set(); - } - - IBlock & firstBlock = *( blocks->begin() ); - BlockDataID pdfFieldID = python_coupling::blockDataIDFromString( firstBlock, pdfFieldIDStr ); - - typedef lbm::D3Q19< lbm::collision_model::SRT, true > LM_SRT_Compressible; - typedef lbm::D3Q19< lbm::collision_model::SRT, false > LM_SRT_Incompressible; - typedef lbm::D3Q19< lbm::collision_model::TRT, true > LM_TRT_Compressible; - typedef lbm::D3Q19< lbm::collision_model::TRT, false > LM_TRT_Incompressible; - - - if (firstBlock.isDataOfType<PdfField<LM_SRT_Compressible>>(pdfFieldID)) { - return make_shared<SweepWrapper>(SplitPureSweep<LM_SRT_Compressible>(pdfFieldID)); - } - else if (firstBlock.isDataOfType<PdfField<LM_SRT_Incompressible>>(pdfFieldID)) { - return make_shared<SweepWrapper>(SplitPureSweep<LM_SRT_Incompressible>(pdfFieldID)); - } - else if (firstBlock.isDataOfType<PdfField<LM_TRT_Compressible>>(pdfFieldID)) { - return make_shared<SweepWrapper>(SplitPureSweep<LM_TRT_Compressible>(pdfFieldID)); - } - else if (firstBlock.isDataOfType<PdfField<LM_TRT_Incompressible>>(pdfFieldID)) { - return make_shared<SweepWrapper>(SplitPureSweep<LM_TRT_Compressible>(pdfFieldID)); - } - else { - PyErr_SetString( PyExc_RuntimeError, "No split-pure sweep available for this lattice model" ); - throw boost::python::error_already_set(); - } - } -} - -template< typename LatticeModels > -struct VelocityAdaptorsFromLatticeModels -{ - typedef typename boost::mpl::transform< LatticeModels, internal::VelocityAdaptorFromLatticeModel<boost::mpl::_1 > >::type type; -}; - -template< typename LatticeModels > -struct DensityAdaptorsFromLatticeModels -{ - typedef typename boost::mpl::transform< LatticeModels, internal::DensityAdaptorFromLatticeModel<boost::mpl::_1> >::type type; -}; - -template< typename LatticeModels > -struct ShearRateAdaptorsFromLatticeModels -{ - typedef typename boost::mpl::transform< LatticeModels, internal::ShearRateAdaptorFromLatticeModel<boost::mpl::_1> >::type type; -}; - -template< typename LatticeModels > -struct AdaptorsFromLatticeModels -{ - typedef typename boost::mpl::copy< typename DensityAdaptorsFromLatticeModels<LatticeModels>::type, - boost::mpl::front_inserter< typename VelocityAdaptorsFromLatticeModels<LatticeModels>::type > > - ::type tmp1; - - typedef typename boost::mpl::copy< typename ShearRateAdaptorsFromLatticeModels<LatticeModels>::type, - boost::mpl::front_inserter< tmp1 > > - ::type type; -}; - - - -template<typename LatticeModels, typename FlagFields> -void exportBasic() -{ - using namespace boost::python; - - python_coupling::ModuleScope scope("lbm"); - - // Lattice Models - exportForceModels(); - exportCollisionModels(); - python_coupling::for_each_noncopyable_type<LatticeModels>( internal::LatticeModelExporter() ); - python_coupling::for_each_noncopyable_type<LatticeModels>( internal::AdaptorExporter() ); - - def( "makeLatticeModel", internal::makeLatticeModel<LatticeModels>, - ( arg("stencil"), arg("collisionModel"), - arg("forceModel")= force_model::None(), - arg("compressible")=false, arg("equilibriumAccuracyOrder")=2) ); - - // PdfField - python_coupling::for_each_noncopyable_type< LatticeModels > ( internal::PdfFieldExporter() ); - def( "addPdfFieldToStorage", &internal::addPdfFieldToStorage<LatticeModels>, - (( arg("blocks") ), - ( arg("name") ), - ( arg("latticeModel") ), - ( arg("initialVelocity") = Vector3<real_t>() ), - ( arg("initialDensity") = real_t(1) ), - ( arg("ghostlayers") = uint_t(1) ), - ( arg("layout") = field::zyxf ), - ( arg("densityAdaptor") = std::string() ), - ( arg("velocityAdaptor") = std::string() ), - ( arg("shearRateAdaptor")= std::string() ) - ) ); - - // Split Sweeps - def("makeSplitPureSweep", &internal::makeSplitPureSweep, (arg("blocks"), arg("pdfFieldId"))); - class_<internal::SweepWrapper, shared_ptr<internal::SweepWrapper>, boost::noncopyable>("SplitPureSweep", no_init) - .def("__call__", &internal::SweepWrapper::operator()); - - - auto pythonManager = python_coupling::Manager::instance(); - pythonManager->addBlockDataConversion< typename AdaptorsFromLatticeModels < LatticeModels >::type > (); -} - - - -} // namespace lbm -} // namespace walberla - - diff --git a/src/lbm/python/ExportBoundary.h b/src/lbm/python/ExportBoundary.h deleted file mode 100644 index a6803a374..000000000 --- a/src/lbm/python/ExportBoundary.h +++ /dev/null @@ -1,39 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportBoundary.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace lbm { - - template<typename LatticeModels, typename FlagFields> - void exportBoundary(); - -} // namespace lbm -} // namespace walberla - - -#include "ExportBoundary.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/lbm/python/ExportBoundary.impl.h b/src/lbm/python/ExportBoundary.impl.h deleted file mode 100644 index 06845e35a..000000000 --- a/src/lbm/python/ExportBoundary.impl.h +++ /dev/null @@ -1,279 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportBoundary.impl.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" -#include "python_coupling/helper/ModuleScope.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" -#include "python_coupling/helper/ConfigFromDict.h" -#include "python_coupling/helper/SliceToCellInterval.h" -#include "python_coupling/Manager.h" - -#include "core/logging/Logging.h" - -#include "boundary/python/Exports.h" - -#include "lbm/boundary/factories/ExtendedBoundaryHandlingFactory.h" - -#include "field/python/FieldExport.h" -#include "field/adaptors/AdaptorCreators.h" - -#include <boost/mpl/transform.hpp> -#include <boost/mpl/copy.hpp> - -namespace walberla { -namespace lbm { - - -namespace internal -{ - using python_coupling::localPythonSliceToCellInterval; - - - template<typename BH> - shared_ptr<BoundaryConfiguration> boundaryConfFromDict( BH & h, const BoundaryUID & uid, boost::python::dict d ) { - shared_ptr<Config> cfg = python_coupling::configFromPythonDict( d ); - return h.createBoundaryConfiguration( uid, cfg->getGlobalBlock() ); - } - template<typename BH> - void BH_setBoundary1( BH & h, const std::string & name, cell_idx_t x, cell_idx_t y , cell_idx_t z, boost::python::dict conf ) { - h.setBoundary( name,x,y,z, *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_setBoundary2( BH & h, const std::string & name, const boost::python::tuple & index, boost::python::dict conf ) { - h.setBoundary( name, - localPythonSliceToCellInterval( *h.getFlagField() , index ), - *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_forceBoundary1( BH & h, const std::string & name, cell_idx_t x, cell_idx_t y , cell_idx_t z, boost::python::dict conf ) { - h.forceBoundary( name,x,y,z, *boundaryConfFromDict( h, name, conf) ); - } - template<typename BH> - void BH_forceBoundary2( BH & h, const std::string & name, const boost::python::tuple & index, boost::python::dict conf ) { - h.forceBoundary( name, - localPythonSliceToCellInterval( *h.getFlagField() , index ), - *boundaryConfFromDict( h, name, conf) ); - } - - template<typename BH> - void BH_forceBoundary3( BH & h, const GhostLayerField<int,1> & indexField , boost::python::dict boundaryInfo ) - { - using namespace boost::python; - list keys = boundaryInfo.keys(); - - std::map<int, FlagUID > flagUIDs; - std::map<int, shared_ptr<BoundaryConfiguration> > boundaryConfigs; - - for (int i = 0; i < len( keys ); ++i) - { - int key = extract<int>( keys[i] ); - extract<std::string> extracted_str_val ( boundaryInfo[key] ); - extract<dict > extracted_dict_val ( boundaryInfo[key] ); - - if ( extracted_str_val.check() ) - { - std::string boundaryName = extracted_str_val; - flagUIDs[key] = FlagUID ( boundaryName ); - } - else if ( extracted_dict_val.check() ) - { - dict info = extracted_dict_val; - std::string boundaryName = extract<std::string>( info["name"] ); - - dict configDict = extract<dict>( info["config"] ); - - flagUIDs[key] = FlagUID ( boundaryName ); - boundaryConfigs[key] = boundaryConfFromDict( h, boundaryName, configDict); - } - else { - PyErr_SetString( PyExc_ValueError, "Invalid parameter"); - throw error_already_set(); - } - } - - // iterate over flag field - if ( indexField.xyzSize() != h.getFlagField()->xyzSize() || indexField.nrOfGhostLayers() > h.getFlagField()->nrOfGhostLayers() ) { - WALBERLA_LOG_DEVEL("indexField " << indexField.xyzSizeWithGhostLayer() ); - WALBERLA_LOG_DEVEL("flagField " << h.getFlagField()->xyzSizeWithGhostLayer() ); - PyErr_SetString( PyExc_ValueError, "Index field has to have same size as flag field"); - throw error_already_set(); - } - - - cell_idx_t gl = cell_idx_c( indexField.nrOfGhostLayers() ); - for( cell_idx_t z = -gl; z < cell_idx_c( indexField.zSizeWithGhostLayer() ); ++z ) - for( cell_idx_t y = -gl; y < cell_idx_c( indexField.ySizeWithGhostLayer() ); ++y ) - for( cell_idx_t x = -gl; x < cell_idx_c( indexField.xSizeWithGhostLayer() ); ++x ) - { - int index = indexField(x,y,z); - if ( flagUIDs.find( index ) != flagUIDs.end() ) - { - if ( boundaryConfigs.find( index ) != boundaryConfigs.end() ) - h.forceBoundary( flagUIDs[index],x,y,z, * boundaryConfigs[index] ); - else - h.forceBoundary( flagUIDs[index],x,y,z ); - } - } - } - - template<typename BH> - void BH_setDomainSlice( BH & h, const boost::python::tuple & index ) { - h.setDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - template<typename BH> - void BH_forceDomainSlice( BH & h, const boost::python::tuple & index ) { - h.forceDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - template<typename BH> - void BH_fillDomainSlice( BH & h, const boost::python::tuple & index ) { - h.fillWithDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_removeDomainSlice( BH & h, const boost::python::tuple & index ) { - h.removeDomain( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_removeBoundarySlice( BH & h, const boost::python::tuple & index ) { - h.removeBoundary( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - template<typename BH> - void BH_clearSlice( BH & h, const boost::python::tuple & index ) { - h.clear( localPythonSliceToCellInterval( *h.getFlagField() , index ) ); - } - - - - - class ExtendedBoundaryHandlingCreator - { - public: - - ExtendedBoundaryHandlingCreator( const shared_ptr<StructuredBlockStorage> & blocks, - const std::string & name, BlockDataID pdfFieldID, BlockDataID flagFieldID ) - : blocks_( blocks ), name_( name ), - pdfFieldID_( pdfFieldID ), flagFieldID_( flagFieldID ), found_ ( false ) - {} - - - template<typename LatticeModel> - void operator()( python_coupling::NonCopyableWrap<LatticeModel> ) - { - using namespace boost::python; - - IBlock & firstBlock = *( blocks_->begin() ); - if ( firstBlock.isDataClassOrSubclassOf< PdfField<LatticeModel> >( pdfFieldID_ ) ) - { - WALBERLA_ASSERT( !found_ ); - ExtendedBoundaryHandlingFactory<LatticeModel, FlagField<uint8_t> >::addBoundaryHandlingToStorage( - blocks_, name_, flagFieldID_, pdfFieldID_, FlagUID("fluid") ); - found_ = true; - } - } - - bool successful() const { return found_; } - - private: - shared_ptr<StructuredBlockStorage> blocks_; - std::string name_; - BlockDataID pdfFieldID_; - BlockDataID flagFieldID_; - - bool found_; - }; - - - template<typename LatticeModels> - void addBoundaryHandlingToStorage( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & name, - const std::string & pdfFieldStringID, - const std::string & flagFieldStringID ) - { - using namespace boost::python; - - if ( bs->begin() == bs->end() ) - return; - - - IBlock & firstBlock = *( bs->begin() ); - - BlockDataID pdfFieldID = python_coupling::blockDataIDFromString( firstBlock, pdfFieldStringID ); - BlockDataID flagFieldID = python_coupling::blockDataIDFromString( firstBlock, flagFieldStringID ); - - if ( ! firstBlock.isDataClassOrSubclassOf< FlagField<uint8_t> >( flagFieldID ) ) - { - PyErr_SetString( PyExc_ValueError, "Unknown FlagField type. Please provide a FlagField with 8 bit per cell"); - throw error_already_set(); - } - - ExtendedBoundaryHandlingCreator creator( bs, name, pdfFieldID, flagFieldID ); - python_coupling::for_each_noncopyable_type<LatticeModels>( std::ref( creator ) ); - - if ( ! creator.successful() ) - { - PyErr_SetString( PyExc_ValueError, "No Boundary Handling found for this lattice model"); - throw error_already_set(); - } - } - - template< typename LatticeModel > - struct ExtendedBoundaryHandlingFromLatticeModel - { - typedef typename ExtendedBoundaryHandlingFactory< LatticeModel, FlagField<uint8_t> >::BoundaryHandling type; - }; - } - - -template< typename LatticeModels > -struct ExtendedBoundaryHandlingsFromLatticeModels -{ - typedef typename boost::mpl::transform< LatticeModels, internal::ExtendedBoundaryHandlingFromLatticeModel<boost::mpl::_1> >::type type; -}; - - -template<typename LatticeModels, typename FlagFields> -void exportBoundary() -{ - using namespace boost::python; - - python_coupling::ModuleScope scope("lbm"); - - // Extended Boundary Handling - boundary::exportModuleToPython< typename ExtendedBoundaryHandlingsFromLatticeModels< LatticeModels >::type >(); - - def ( "addBoundaryHandlingToStorage", &internal::addBoundaryHandlingToStorage<LatticeModels>, - ( arg("blocks"), - arg("name"), - arg("pdfField"), - arg("flagField") ) ); - - auto pythonManager = python_coupling::Manager::instance(); - pythonManager->addBlockDataConversion< typename ExtendedBoundaryHandlingsFromLatticeModels< LatticeModels >::type > (); -} - - - -} // namespace lbm -} // namespace walberla - - diff --git a/src/lbm/python/ExportSweeps.h b/src/lbm/python/ExportSweeps.h deleted file mode 100644 index ba33e4803..000000000 --- a/src/lbm/python/ExportSweeps.h +++ /dev/null @@ -1,39 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportSweeps.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace lbm { - - template<typename LatticeModels, typename FlagFields> - void exportSweeps(); - -} // namespace lbm -} // namespace walberla - - -#include "ExportSweeps.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/lbm/python/ExportSweeps.impl.h b/src/lbm/python/ExportSweeps.impl.h deleted file mode 100644 index 5f9440114..000000000 --- a/src/lbm/python/ExportSweeps.impl.h +++ /dev/null @@ -1,260 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExportSweeps.impl.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BoostPythonHelpers.h" -#include "python_coupling/helper/ModuleScope.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" - -#include "python_coupling/Manager.h" - -#include "core/logging/Logging.h" - -#include "lbm/lattice_model/CollisionModel.h" -#include "lbm/sweeps/CellwiseSweep.h" - -#include "field/python/FieldExport.h" -#include "field/adaptors/AdaptorCreators.h" - -namespace walberla { -namespace lbm { - - -namespace internal -{ - template< typename LM, typename Filter, typename In, typename Out > - void addSweepMemberFunctions( boost::python::class_< CellwiseSweep<LM,Filter,In,Out>, shared_ptr<CellwiseSweep<LM,Filter,In,Out> > > & c ) - { - using namespace boost::python; - - typedef CellwiseSweep<LM,Filter,In,Out> Sweep; - c.def( "stream", &Sweep::stream, (arg("block"),arg("numberOfGhostLayersToInclude")=uint_t(0) ) ) - .def( "collide", &Sweep::collide, (arg("block"),arg("numberOfGhostLayersToInclude")=uint_t(0) ) ) - .def("streamCollide", &Sweep::streamCollide, (arg("block"),arg("numberOfGhostLayersToInclude")=uint_t(0) ) ) - .def("__call__", &Sweep::operator(), (arg("block"),arg("numberOfGhostLayersToInclude")=uint_t(0) ) ) - ; - } - - struct CellwiseSweepExporterWithoutFlagField - { - template<typename LatticeModel_T> - void operator()( python_coupling::NonCopyableWrap<LatticeModel_T> ) - { - using namespace boost::python; - - typedef GhostLayerField< real_t, 3 > VelocityField_T; - - typedef CellwiseSweep<LatticeModel_T, - field::DefaultEvaluationFilter, - DefaultDensityEquilibriumVelocityCalculation, - DefaultDensityVelocityCallback - > Sweep1; - typedef CellwiseSweep<LatticeModel_T, - field::DefaultEvaluationFilter, - DefaultDensityEquilibriumVelocityCalculation, - VelocityCallback<VelocityField_T> - > Sweep2; - - auto sweep1 = class_< Sweep1, shared_ptr<Sweep1> > ( "CellwiseSweep", no_init ); - auto sweep2 = class_< Sweep2, shared_ptr<Sweep2> > ( "CellwiseSweep", no_init ); - addSweepMemberFunctions( sweep1 ); - addSweepMemberFunctions( sweep2 ); - } - }; - - struct CellwiseSweepExporterWithFlagField - { - template<typename LatticeModel_FlagField_Pair> - void operator()( python_coupling::NonCopyableWrap<LatticeModel_FlagField_Pair> ) - { - using namespace boost::python; - - typedef typename LatticeModel_FlagField_Pair::first LatticeModel_T; - typedef typename LatticeModel_FlagField_Pair::second FlagField_T; - - typedef GhostLayerField< real_t,3> VelocityField_T; - - typedef CellwiseSweep<LatticeModel_T, - field::FlagFieldEvaluationFilter<FlagField_T>, - DefaultDensityEquilibriumVelocityCalculation, - DefaultDensityVelocityCallback - > Sweep1; - typedef CellwiseSweep<LatticeModel_T, - field::FlagFieldEvaluationFilter<FlagField_T>, - DefaultDensityEquilibriumVelocityCalculation, - VelocityCallback<VelocityField_T> - > Sweep2; - - auto sweep1 = class_< Sweep1, shared_ptr<Sweep1> > ( "CellwiseSweep", no_init ); - auto sweep2 = class_< Sweep2, shared_ptr<Sweep2> > ( "CellwiseSweep", no_init ); - addSweepMemberFunctions( sweep1 ); - addSweepMemberFunctions( sweep2 ); - } - }; - - - class CellwiseSweepCreator - { - public: - - CellwiseSweepCreator( const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID pdfFieldID, - const std::string & flagFieldStringID, const std::string & velocityFieldStringID, - const Set< FlagUID > & cellsToEvaluate ) - : blocks_( blocks ), pdfFieldID_( pdfFieldID ), flagFieldStringID_( flagFieldStringID ), - velocityFieldStringID_( velocityFieldStringID ), cellsToEvaluate_( cellsToEvaluate ) - {} - - - template<typename LatticeModel_FlagField_Pair> - void operator()( python_coupling::NonCopyableWrap<LatticeModel_FlagField_Pair> ) - { - typedef GhostLayerField<real_t,3> VelocityField_T; - - using namespace boost::python; - using python_coupling::blockDataIDFromString; - - - typedef typename LatticeModel_FlagField_Pair::first LatticeModel_T; - typedef typename LatticeModel_FlagField_Pair::second FlagField_T; - - if ( blocks_->begin() == blocks_->end() ) - return; - - IBlock & firstBlock = *( blocks_->begin() ); - - if( ! firstBlock.isDataClassOrSubclassOf<PdfField<LatticeModel_T> >(pdfFieldID_) ) - return; - - if( flagFieldStringID_.size() > 0 ) - { - // Use FlagFilter - BlockDataID flagFieldID = blockDataIDFromString( firstBlock, flagFieldStringID_ ); - if ( ! firstBlock.isDataClassOrSubclassOf< FlagField_T>( flagFieldID ) ) - return; - - if ( velocityFieldStringID_.size() > 0 ) - { - BlockDataID velocityFieldID = blockDataIDFromString( firstBlock, velocityFieldStringID_ ); - result_ = object( makeCellwiseSweep<LatticeModel_T, FlagField_T, VelocityField_T >( pdfFieldID_, flagFieldID, - cellsToEvaluate_, velocityFieldID ) ); - } - else - { - result_ = object( makeCellwiseSweep<LatticeModel_T, FlagField_T>( pdfFieldID_, flagFieldID, cellsToEvaluate_ ) ); - } - } - else - { - if ( velocityFieldStringID_.size() > 0 ) - { - BlockDataID velocityFieldID = blockDataIDFromString( firstBlock, velocityFieldStringID_ ); - result_ = object ( makeCellwiseSweep<LatticeModel_T, - field::DefaultEvaluationFilter, - DefaultDensityEquilibriumVelocityCalculation, - VelocityCallback<VelocityField_T> - > - ( pdfFieldID_, field::DefaultEvaluationFilter(), - DefaultDensityEquilibriumVelocityCalculation(), - VelocityCallback<VelocityField_T>( velocityFieldID ) ) ); - } - else - { - result_ = object( makeCellwiseSweep<LatticeModel_T>( pdfFieldID_ ) ); - } - } - } - - boost::python::object getResult() const { return result_; } - - private: - shared_ptr<StructuredBlockStorage> blocks_; - BlockDataID pdfFieldID_; - std::string flagFieldStringID_; - std::string velocityFieldStringID_; - Set< FlagUID > cellsToEvaluate_; - - boost::python::object result_; - }; - - - template<typename LatticeModel_FlagField_Pairs> - boost::python::object makeCellwiseSweep( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & pdfFieldStringID, - const std::string & flagFieldStringID, boost::python::list flagList, - const std::string & velocityFieldStringID ) - { - using namespace boost::python; - - if ( bs->begin() == bs->end() ) - return object(); - IBlock & firstBlock = *( bs->begin() ); - - if( flagFieldStringID.size() > 0 || len(flagList) > 0 ) - { - if ( !( flagFieldStringID.size() > 0 && len(flagList) > 0 ) ) - { - PyErr_SetString( PyExc_ValueError, "Pass flagFieldID and flagList or none of them."); - throw error_already_set(); - } - } - - BlockDataID pdfFieldID = python_coupling::blockDataIDFromString( firstBlock, pdfFieldStringID ); - - auto flagUidSet = python_coupling::uidSetFromStringContainer< FlagUID >( flagList ); - - CellwiseSweepCreator creator( bs, pdfFieldID, flagFieldStringID, velocityFieldStringID, flagUidSet ); - python_coupling::for_each_noncopyable_type<LatticeModel_FlagField_Pairs>( std::ref( creator ) ); - - if ( creator.getResult() == object() ) - { - PyErr_SetString( PyExc_ValueError, "No Sweep available with these options."); - throw error_already_set(); - } - return creator.getResult(); - } - -} - -template<typename LatticeModels, typename FlagFields> -void exportSweeps() -{ - using namespace boost::python; - - - python_coupling::ModuleScope scope("lbm"); - - // Cellwise Sweep - typedef python_coupling::combine_vectors<LatticeModels,FlagFields> LatticeModel_FlagField_Pairs; - python_coupling::for_each_noncopyable_type<LatticeModel_FlagField_Pairs>( internal::CellwiseSweepExporterWithFlagField() ); - python_coupling::for_each_noncopyable_type<LatticeModels> ( internal::CellwiseSweepExporterWithoutFlagField() ); - def( "makeCellwiseSweep", internal::makeCellwiseSweep<LatticeModel_FlagField_Pairs>, - ( arg("blocks"), arg("pdfFieldID"), - arg("flagFieldID")=std::string(), arg("flagList")=boost::python::list(), - arg("velocityFieldID")=std::string() ) ); - -} - - - -} // namespace lbm -} // namespace walberla - - diff --git a/src/lbm/python/Exports.h b/src/lbm/python/Exports.h deleted file mode 100644 index bbf41d3b2..000000000 --- a/src/lbm/python/Exports.h +++ /dev/null @@ -1,49 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file PythonExports.h -//! \ingroup lbm -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "ExportBasic.h" -#include "ExportSweeps.h" -#include "ExportBoundary.h" - - -namespace walberla { -namespace lbm { - - - template<typename LatticeModels, typename FlagFields> - void exportModuleToPython() - { - exportBasic<LatticeModels,FlagFields>(); - exportSweeps<LatticeModels,FlagFields>(); - exportBoundary<LatticeModels,FlagFields>(); - } - - -} // namespace lbm -} // namespace walberla - - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/mesh/python/Exports.h b/src/mesh/python/Exports.h deleted file mode 100644 index 0ae7cfd3a..000000000 --- a/src/mesh/python/Exports.h +++ /dev/null @@ -1,41 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.h -//! \ingroup mesh -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace mesh { - - - template<typename FlagFields> - void exportModuleToPython(); - - -} // namespace mesh -} // namespace walberla - - -#include "Exports.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/mesh/python/Exports.impl.h b/src/mesh/python/Exports.impl.h deleted file mode 100644 index 4c68eb850..000000000 --- a/src/mesh/python/Exports.impl.h +++ /dev/null @@ -1,186 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.impl.h -//! \ingroup mesh -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" -#include "python_coupling/helper/ModuleScope.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON -#ifdef WALBERLA_BUILD_WITH_OPENMESH - -#include "python_coupling/Manager.h" -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" -#include "python_coupling/helper/PythonIterableToStdVector.h" -#include "python_coupling/helper/SharedPtrDeleter.h" - -#ifdef _MSC_VER -# pragma warning( push ) -# pragma warning( disable : 4456 4459 ) -#endif - -#include <OpenMesh/Core/IO/MeshIO.hh> -#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> - -#ifdef _MSC_VER -# pragma warning( pop ) -#endif - -#include "mesh_common/MeshIO.h" -#include "mesh_common/distance_octree/DistanceOctree.h" -#include "mesh_common/DistanceComputations.h" -#include "mesh_common/DistanceFunction.h" -#include "mesh_common/MeshOperations.h" -#include "mesh_common/TriangleMeshes.h" -#include "mesh/boundary/BoundarySetup.h" -#include "mesh_common/vtk/VTKMeshWriter.h" - -using namespace boost::python; - - -namespace walberla { -namespace mesh { - -typedef mesh::DistanceOctree<PythonTriangleMesh> Octree; -typedef mesh::VTKMeshWriter<PythonTriangleMesh> MeshWriter; - -namespace internal -{ - void exp_readAndBroadcast(const std::string & fileName, PythonTriangleMesh & mesh ) - { - mesh::readAndBroadcast(fileName, mesh); - } - - - shared_ptr<Octree> makeDistanceOctree(object meshObj, uint_t maxDepth, uint_t minNumTriangles) - { - auto mesh = python_coupling::createSharedPtrFromPythonObject<PythonTriangleMesh>(meshObj); - auto triangleDistance = make_shared<TriangleDistance<PythonTriangleMesh> > (mesh); - return make_shared< Octree >(triangleDistance, maxDepth, minNumTriangles ); - } - - shared_ptr<MeshWriter> makeVTKMeshWriter(object meshObj, const std::string & identifier, const uint_t writeFrequency, const std::string & baseFolder) - { - shared_ptr<PythonTriangleMesh> mesh = python_coupling::createSharedPtrFromPythonObject<PythonTriangleMesh>(meshObj); - return walberla::make_shared< MeshWriter >( mesh, identifier, writeFrequency, baseFolder ); - } - - // ----------------------------------- markFlagField ---------------------------------------------------------------- - - - template<typename FlagField> - void markFlagFieldTmpl(const shared_ptr<Octree> & octree, const shared_ptr<StructuredBlockStorage> & bs, BlockDataID flagFieldID, - const std::string & flagName, uint_t ghostLayers) - { - BoundarySetup setup( bs, makeMeshDistanceFunction( octree ), ghostLayers); - setup.setFlag<FlagField>(flagFieldID, flagName, BoundarySetup::INSIDE); - } - - FunctionExporterClass( markFlagFieldTmpl, - void ( const shared_ptr<Octree> &, const shared_ptr<StructuredBlockStorage> & , - BlockDataID ,const std::string &, uint_t ) ); - - template<typename FlagFields> - void exp_markFlagField(const shared_ptr<Octree> & octree, const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataStr, const std::string & flagName, uint_t ghostLayers) - { - if ( bs->begin() == bs->end() ) - return; - IBlock * firstBlock = & ( * bs->begin() ); - - auto fieldID = python_coupling::blockDataIDFromString( *bs, blockDataStr ); - python_coupling::Dispatcher<FlagFields, Exporter_markFlagFieldTmpl > dispatcher( firstBlock ); - dispatcher( fieldID )(octree, bs, fieldID, flagName, ghostLayers); - } -} - - -bool Octree_isAABBFullyInside(const Octree & octree, const AABB & aabb) -{ - for( auto corner: aabb.corners() ) - { - const Octree::Point p ( numeric_cast<Octree::Scalar>(corner[0]), - numeric_cast<Octree::Scalar>(corner[1]), - numeric_cast<Octree::Scalar>(corner[2]) ); - if( octree.sqSignedDistance(p) > 0 ) - return false; - } - return true; -} - - -bool Octree_isAABBFullyOutside(const Octree & octree, const AABB & aabb) -{ - for( auto corner: aabb.corners() ) - { - const Octree::Point p ( numeric_cast<Octree::Scalar>(corner[0]), - numeric_cast<Octree::Scalar>(corner[1]), - numeric_cast<Octree::Scalar>(corner[2]) ); - if( octree.sqSignedDistance(p) < 0 ) - return false; - } - return true; -} - - -template<typename FlagFields> -void exportModuleToPython() -{ - python_coupling::ModuleScope fieldModule( "mesh" ); - - def( "readAndBroadcast", &internal::exp_readAndBroadcast, (arg("fileName"), arg("mesh")) ); - - def( "aabb", &mesh::computeAABB<PythonTriangleMesh>, (arg("mesh")) ); - def( "translate", &mesh::translate<PythonTriangleMesh>, (arg("mesh"), arg("translationVector")) ); - def( "scale", &mesh::scale<PythonTriangleMesh>, (arg("mesh"), arg("scaleFactors")) ); - def( "volume", &mesh::computeVolume<PythonTriangleMesh>, (arg("mesh")) ); - def( "surfaceArea", &mesh::computeSurfaceArea<PythonTriangleMesh>, (arg("mesh")) ); - - def( "markFlagField", &internal::exp_markFlagField<FlagFields> ); - - Octree::Scalar (Octree::*sqSignedDistance1)(const Octree::Point&) const= &Octree::sqSignedDistance; - Octree::Scalar (Octree::*sqDistance1)(const Octree::Point&) const= &Octree::sqDistance; - - class_<Octree, shared_ptr<Octree> >("DistanceOctree", no_init) - .def("__init__", make_constructor(internal::makeDistanceOctree, default_call_policies() ,(arg("mesh"), arg("maxDepth")=20u, arg("minNumTriangles")=25u) ) ) - .def("sqSignedDistance", sqSignedDistance1, (arg("p"))) - .def("sqDistance", sqDistance1, (arg("p"))) - .def("height", &Octree::height) - .def("writeVTKOutput", &Octree::writeVTKOutput, (arg("filestem"))) - .def("isAABBfullyOutside", &Octree_isAABBFullyOutside) - .def("isAABBfullyInside", &Octree_isAABBFullyInside) - ; - - class_<MeshWriter, shared_ptr<MeshWriter> >("VTKMeshWriter", no_init) - .def("__init__", make_constructor(internal::makeVTKMeshWriter, default_call_policies() ,(arg("mesh"), arg("identifier"), arg("writeFrequency")=1u, arg("baseFolder")="vtk_out") ) ) - .def("__call__", &MeshWriter::operator()) - ; - -} - - - -} // namespace mesh -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON -#endif //WALBERLA_BUILD_WITH_OPENMESH \ No newline at end of file diff --git a/src/postprocessing/python/Exports.h b/src/postprocessing/python/Exports.h deleted file mode 100644 index 17cd6b07a..000000000 --- a/src/postprocessing/python/Exports.h +++ /dev/null @@ -1,41 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.h -//! \ingroup postprocessing -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace postprocessing { - - - template<typename RealFields, typename FlagFields> - void exportModuleToPython(); - - -} // namespace postprocessing -} // namespace walberla - - -#include "Exports.impl.h" - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/postprocessing/python/Exports.impl.h b/src/postprocessing/python/Exports.impl.h deleted file mode 100644 index 4f67e756e..000000000 --- a/src/postprocessing/python/Exports.impl.h +++ /dev/null @@ -1,141 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.impl.h -//! \ingroup geometry -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" -#include "python_coupling/helper/ModuleScope.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/Manager.h" -#include "python_coupling/helper/MplHelpers.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" -#include "python_coupling/helper/PythonIterableToStdVector.h" - - -#include "postprocessing/FieldToSurfaceMesh.h" - -using namespace boost::python; - - -namespace walberla { -namespace postprocessing { - -FunctionExporterClass( realFieldToSurfaceMesh, - shared_ptr<geometry::TriangleMesh>( const shared_ptr<StructuredBlockStorage> &, - ConstBlockDataID, real_t, uint_t, bool, int, MPI_Comm ) ); - -template<typename FieldVector> -static shared_ptr<geometry::TriangleMesh> exp_realFieldToSurfaceMesh - ( const shared_ptr<StructuredBlockStorage> & bs, const std::string & blockDataStr, real_t threshold, - uint_t fCoord = 0, bool calcNormals = false, int targetRank = 0 ) -{ - if ( bs->begin() == bs->end() ) - return shared_ptr<geometry::TriangleMesh>(); - IBlock * firstBlock = & ( * bs->begin() ); - - auto fieldID = python_coupling::blockDataIDFromString( *bs, blockDataStr ); - - python_coupling::Dispatcher<FieldVector, Exporter_realFieldToSurfaceMesh > dispatcher( firstBlock ); - return dispatcher( fieldID )( bs, fieldID, threshold, fCoord, calcNormals, targetRank, MPI_COMM_WORLD) ; -} - - -template< typename FField> -typename FField::value_type maskFromFlagList( const shared_ptr<StructuredBlockStorage> & bs, - ConstBlockDataID flagFieldID, - const std::vector< std::string > & flagList ) -{ - if ( bs->begin() == bs->end() ) - return 0; - - IBlock & firstBlock = *( bs->begin() ); - const FField * flagField = firstBlock.getData< const FField > ( flagFieldID ); - - typedef typename FField::flag_t flag_t; - flag_t mask = 0; - for( auto it = flagList.begin(); it != flagList.end(); ++it ) - { - if ( ! flagField->flagExists( *it ) ) - throw python_coupling::BlockDataNotConvertible( "Unknown FlagID" ); - - mask = flag_t( mask | flagField->getFlag( *it ) ); - } - - return mask; -} - - -template<typename FlagField_T> -boost::python::object adaptedFlagFieldToSurfaceMesh( const shared_ptr<StructuredBlockStorage> & bs, - ConstBlockDataID fieldID, const std::vector< std::string > & flagList, - bool calcNormals = false, int targetRank = 0 ) -{ - using namespace boost::python; - - auto mask = maskFromFlagList<FlagField_T>( bs, fieldID, flagList ); - return object( flagFieldToSurfaceMesh<FlagField_T>(bs, fieldID, mask, calcNormals, targetRank ) ); -} - - -FunctionExporterClass( adaptedFlagFieldToSurfaceMesh, - boost::python::object( const shared_ptr<StructuredBlockStorage> &, ConstBlockDataID, - const std::vector< std::string > &, bool,int ) ); - - -template<typename FieldVector> -static boost::python::object exp_flagFieldToSurfaceMesh ( const shared_ptr<StructuredBlockStorage> & bs, - const std::string & blockDataName, - const boost::python::list & flagList, - bool calcNormals = false, int targetRank = 0 ) -{ - if ( bs->begin() == bs->end() ) - return boost::python::object(); //TODO check if this is correct - - IBlock * firstBlock = & ( * bs->begin() ); - - auto fieldID = python_coupling::blockDataIDFromString( *bs, blockDataName ); - - auto flagVector = python_coupling::pythonIterableToStdVector<std::string>( flagList ); - python_coupling::Dispatcher<FieldVector, Exporter_adaptedFlagFieldToSurfaceMesh > dispatcher( firstBlock ); - return dispatcher( fieldID )( bs, fieldID, flagVector, calcNormals, targetRank ); -} - - - -template<typename RealFields, typename FlagFields> -void exportModuleToPython() -{ - python_coupling::ModuleScope fieldModule( "postprocessing" ); - - def( "realFieldToMesh", &exp_realFieldToSurfaceMesh<RealFields>, - ( arg("blocks"), arg("blockDataName"), arg("fCoord")=0, arg("calcNormals") = false, arg("targetRank")=0 ) ); - - def( "flagFieldToMesh", &exp_flagFieldToSurfaceMesh<FlagFields>, - ( arg("blocks"), arg("blockDataName"), arg("flagList"), arg("calcNormals") = false, arg("targetRank")=0 ) ); -} - - - -} // namespace postprocessing -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/python_coupling/CMakeLists.txt b/src/python_coupling/CMakeLists.txt index 2b303c171..882a6d559 100644 --- a/src/python_coupling/CMakeLists.txt +++ b/src/python_coupling/CMakeLists.txt @@ -1,3 +1,5 @@ -waLBerla_add_module( DEPENDS core communication domain_decomposition stencil ) - - \ No newline at end of file +if (WALBERLA_BUILD_WITH_PYTHON) + waLBerla_add_module(DEPENDS core communication domain_decomposition stencil field blockforest vtk cuda) + target_link_libraries(python_coupling PUBLIC pybind11::embed core domain_decomposition) + set_target_properties(python_coupling PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() \ No newline at end of file diff --git a/src/python_coupling/CreateConfig.cpp b/src/python_coupling/CreateConfig.cpp index 0a4416c33..c7e3d1b24 100644 --- a/src/python_coupling/CreateConfig.cpp +++ b/src/python_coupling/CreateConfig.cpp @@ -16,248 +16,204 @@ //! \file CreateConfigFromPythonScript.cpp //! \ingroup python //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #include "CreateConfig.h" - +#include "core/StringUtility.h" #include "core/config/Config.h" #include "core/config/Create.h" #include "core/logging/Logging.h" -#include "core/StringUtility.h" - -#include <exception> - #ifdef WALBERLA_BUILD_WITH_PYTHON +# include "python_coupling/helper/ConfigFromDict.h" -#include "PythonCallback.h" -#include "PythonWrapper.h" -#include "DictWrapper.h" +# include "PythonCallback.h" +# include <pybind11/pybind11.h> -#include "python_coupling/helper/ConfigFromDict.h" -#include "helper/ExceptionHandling.h" +namespace walberla +{ +namespace python_coupling +{ +namespace py = pybind11; +shared_ptr< Config > createConfigFromPythonScript(const std::string& scriptFile, + const std::string& pythonFunctionName, + const std::vector< std::string >& argv) +{ + importModuleOrFile(scriptFile, argv); -namespace walberla { -namespace python_coupling { + PythonCallback pythonCallback(pythonFunctionName); - namespace bp = boost::python; + pythonCallback(); + using py::dict; + using py::object; - shared_ptr<Config> createConfigFromPythonScript( const std::string & scriptFile, - const std::string & pythonFunctionName, - const std::vector<std::string> & argv ) - { - importModuleOrFile( scriptFile, argv ); - - PythonCallback pythonCallback ( pythonFunctionName ); - - pythonCallback(); + object returnValue = pythonCallback.data().dict()["returnValue"]; + if (returnValue.is(object())) return shared_ptr< Config >(); - using boost::python::object; - using boost::python::dict; - using boost::python::extract; - - object returnValue = pythonCallback.data().dict()[ "returnValue" ]; - if ( returnValue == object() ) - return shared_ptr<Config>(); + bool isDict = py::isinstance< dict >(returnValue); + if (!isDict) { WALBERLA_ABORT("Python configuration did not return a dictionary object."); } + dict returnDict = dict(returnValue); + return configFromPythonDict(returnDict); +} - bool isDict = extract< dict >( returnValue ).check(); - if ( ! isDict ) { - WALBERLA_ABORT("Python configuration did not return a dictionary object."); - } - dict returnDict = extract<dict>( returnValue ); - return configFromPythonDict( returnDict ); - } - - - //=================================================================================================================== - // - // Config Generators and iterators - // - //=================================================================================================================== +//=================================================================================================================== +// +// Config Generators and iterators +// +//=================================================================================================================== +class PythonMultipleConfigGenerator : public config::ConfigGenerator +{ + public: + PythonMultipleConfigGenerator(py::list ConfigList) // NOLINT + : ConfigList_(ConfigList), counter(0) // NOLINT + {} - class PythonMultipleConfigGenerator : public config::ConfigGenerator + shared_ptr< Config > next() override { - public: - PythonMultipleConfigGenerator( bp::stl_input_iterator< bp::dict > iterator ) //NOLINT - : iter_( iterator ), firstTime_(true) //NOLINT - {} + shared_ptr< Config > config = make_shared< Config >(); - shared_ptr<Config> next() override - { - // this seemingly unnecessary complicated firstTime variable is used - // since in alternative version where (++iter_) is at the end of the function - // the python generator expression for the next time is already - // called before the current simulation finished - if ( !firstTime_ ) - ++iter_; - else - firstTime_ = false; + if ( counter == ConfigList_.size() ) + return shared_ptr<Config>(); - if ( iter_ == bp::stl_input_iterator< bp::dict >() ) - return shared_ptr<Config>(); + py::dict configDict = ConfigList_[counter]; + configFromPythonDict(config->getWritableGlobalBlock(), configDict); - shared_ptr<Config> config = make_shared<Config>(); + WALBERLA_LOG_INFO_ON_ROOT("Simulating Scenario " << counter + 1 << " of " << ConfigList_.size() << ":") - bp::dict configDict = *iter_; - configFromPythonDict( config->getWritableGlobalBlock(), configDict ); + counter++; - return config; - } - - private: - bp::stl_input_iterator< bp::dict > iter_; - bool firstTime_; - }; + return config; + } + private: + py::list ConfigList_; + uint_t counter; +}; +class PythonSingleConfigGenerator : public config::ConfigGenerator +{ + public: + PythonSingleConfigGenerator(const shared_ptr< Config >& config) : config_(config) {} - class PythonSingleConfigGenerator : public config::ConfigGenerator + shared_ptr< Config > next() override { - public: - PythonSingleConfigGenerator( const shared_ptr<Config> & config ): config_ ( config ) {} + auto res = config_; + config_.reset(); + return res; + } - shared_ptr<Config> next() override - { - auto res = config_; - config_.reset(); - return res; - } + private: + shared_ptr< Config > config_; +}; - private: - shared_ptr<Config> config_; - }; +config::Iterator createConfigIteratorFromPythonScript(const std::string& scriptFile, + const std::string& pythonFunctionName, + const std::vector< std::string >& argv) +{ + importModuleOrFile(scriptFile, argv); + PythonCallback pythonCallback(pythonFunctionName); + pythonCallback(); + py::object returnValue = pythonCallback.data().dict()["returnValue"]; + bool isDict = py::isinstance< py::dict >(returnValue); - config::Iterator createConfigIteratorFromPythonScript( const std::string & scriptFile, - const std::string & pythonFunctionName, - const std::vector<std::string> & argv ) + shared_ptr< config::ConfigGenerator > generator; + if (isDict) { - importModuleOrFile( scriptFile, argv ); - - PythonCallback pythonCallback ( pythonFunctionName ); - - pythonCallback(); - - bp::object returnValue = pythonCallback.data().dict()[ "returnValue" ]; - - bool isDict = bp::extract< bp::dict >( returnValue ).check(); - - shared_ptr< config::ConfigGenerator> generator; - if ( isDict ) + auto config = make_shared< Config >(); + py::dict extractedDict = py::cast< py::dict >(returnValue); + configFromPythonDict(config->getWritableGlobalBlock(), extractedDict); + generator = make_shared< PythonSingleConfigGenerator >(config); + } + else + { + try { - auto config = make_shared<Config>(); - bp::dict extractedDict = bp::extract<bp::dict> ( returnValue ); - configFromPythonDict( config->getWritableGlobalBlock(), extractedDict ); - generator = make_shared<PythonSingleConfigGenerator>( config ); - } - else { - - try { - generator= make_shared<PythonMultipleConfigGenerator>( returnValue ); - } - catch ( bp::error_already_set & ) { - python_coupling::terminateOnPythonException("Error while running Python config generator"); - } + generator = make_shared< PythonMultipleConfigGenerator >(returnValue); + } catch (py::error_already_set&) + { + std::string message = std::string("Error while running Python function ") + pythonFunctionName; + WALBERLA_ABORT_NO_DEBUG_INFO(message); } - - return config::Iterator( generator ); } + return config::Iterator(generator); +} } // namespace python_coupling } // namespace walberla - #else - -namespace walberla { -namespace python_coupling { - - shared_ptr<Config> createConfigFromPythonScript( const std::string &, const std::string &, const std::vector<std::string> & ) - { - WALBERLA_ABORT( "Tried to run with Python config but waLBerla was built without Python support." ); - return shared_ptr<Config>(); - } - - - config::Iterator createConfigIteratorFromPythonScript( const std::string & , const std::string &, const std::vector<std::string> & ) - { - WALBERLA_ABORT( "Tried to run with Python config but waLBerla was built without Python support." ); - return config::Iterator(); - } - +namespace walberla +{ +namespace python_coupling +{ +shared_ptr< Config > createConfigFromPythonScript(const std::string&, const std::string&, + const std::vector< std::string >&) +{ + WALBERLA_ABORT("Tried to run with Python config but waLBerla was built without Python support."); + return shared_ptr< Config >(); +} + +config::Iterator createConfigIteratorFromPythonScript(const std::string&, const std::string&, + const std::vector< std::string >&) +{ + WALBERLA_ABORT("Tried to run with Python config but waLBerla was built without Python support."); + return config::Iterator(); +} } // namespace python_coupling } // namespace walberla - #endif +namespace walberla +{ +namespace python_coupling +{ +shared_ptr< Config > createConfig(int argc, char** argv) +{ + if (argc < 2) throw std::runtime_error(config::usageString(argv[0])); + shared_ptr< Config > config; + std::string filename(argv[1]); + auto argVec = std::vector< std::string >(argv + 1, argv + argc); - - - - - - - - -namespace walberla { -namespace python_coupling { - - - - shared_ptr<Config> createConfig( int argc, char ** argv ) + if (string_ends_with(filename, ".py")) { config = createConfigFromPythonScript(filename, "config", argVec); } + else { - if(argc<2) - throw std::runtime_error( config::usageString(argv[0]) ); + config = make_shared< Config >(); + config::createFromTextFile(*config, filename); + } - shared_ptr<Config> config; - std::string filename( argv[1] ); + config::substituteCommandLineArgs(*config, argc, argv); - auto argVec = std::vector<std::string> (argv+1, argv + argc); + return config; +} - if ( string_ends_with( filename, ".py") ) { - config = createConfigFromPythonScript( filename, "config", argVec ); - } - else { - config = make_shared<Config>(); - config::createFromTextFile( *config, filename ); - } +config::Iterator configBegin(int argc, char** argv) +{ + if (argc < 2) throw std::runtime_error(config::usageString(argv[0])); - config::substituteCommandLineArgs( *config, argc, argv ); - - return config; + std::string filename(argv[1]); + if (string_ends_with(filename, ".py")) + { + auto argVec = std::vector< std::string >(argv + 1, argv + argc); + return createConfigIteratorFromPythonScript(filename, "config", argVec); } - - config::Iterator configBegin( int argc, char ** argv ) + else { - if(argc<2) - throw std::runtime_error( config::usageString(argv[0]) ); - - std::string filename( argv[1] ); - if ( string_ends_with( filename, ".py") ) { - auto argVec = std::vector<std::string> (argv+1, argv + argc); - return createConfigIteratorFromPythonScript( filename, "config", argVec ); - } - else { - return config::begin( argc, argv ); - } - + return config::begin(argc, argv); } - - - - +} } // namespace python_coupling } // namespace walberla - diff --git a/src/python_coupling/CreateConfig.h b/src/python_coupling/CreateConfig.h index 4c7dec116..f95d3586e 100644 --- a/src/python_coupling/CreateConfig.h +++ b/src/python_coupling/CreateConfig.h @@ -15,7 +15,8 @@ // //! \file CreateConfigFromPythonScript.h //! \ingroup python -//! \author martin +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> //! \brief Creates a walberla::Config object from a python script // //====================================================================================================================== diff --git a/src/python_coupling/DictWrapper.h b/src/python_coupling/DictWrapper.h index 0d2f0cab1..c3e1c2686 100644 --- a/src/python_coupling/DictWrapper.h +++ b/src/python_coupling/DictWrapper.h @@ -16,7 +16,8 @@ //! \file DictWrapper.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> -//! \brief Wrapper to store and extract values from boost::python::dict +//! \author Markus Holzer <markus.holzer@fau.de> +//! \brief Wrapper to store and extract values from pybind11 // //! \warning: if you include this header you also have to include Python.h as first header in your //! cpp file @@ -34,8 +35,6 @@ namespace walberla { namespace python_coupling { - - class DictWrapper { public: @@ -43,9 +42,9 @@ namespace python_coupling { //** Expose Data ************************************************************************************************* /*! \name Expose Data */ //@{ - template<typename T> inline void exposePtr( const std::string & name, T * var ); - template<typename T> inline void exposePtr( const std::string & name, const shared_ptr<T> & var ); - template<typename T> void exposeValue ( const std::string & name, const T & var ); + template<typename T> inline void exposePtr(const char* name, T * var ); + template<typename T> inline void exposePtr(const char* name, const shared_ptr<T> & var ); + template<typename T> void exposeValue ( const char* name, const T & var ); //@} //**************************************************************************************************************** @@ -53,19 +52,19 @@ namespace python_coupling { //** Get Data *************************************************************************************************** /*! \name Get Data */ //@{ - template<typename T> inline T get( const std::string & name ); - template<typename T> inline bool has( const std::string & name ); - template<typename T> inline bool checkedGet( const std::string & name, T output ); + template<typename T> inline T get( const char* name ); + template<typename T> inline bool has( const char* name ); + template<typename T> inline bool checkedGet( const char* name, T output ); //@} //**************************************************************************************************************** #ifdef WALBERLA_BUILD_WITH_PYTHON public: - boost::python::dict & dict() { return d_; } - const boost::python::dict & dict() const { return d_; } + pybind11::dict & dict() { return d_; } + const pybind11::dict & dict() const { return d_; } protected: - boost::python::dict d_; + pybind11::dict d_; #endif }; diff --git a/src/python_coupling/DictWrapper.impl.h b/src/python_coupling/DictWrapper.impl.h index b6ffecb9b..a2c8530c7 100644 --- a/src/python_coupling/DictWrapper.impl.h +++ b/src/python_coupling/DictWrapper.impl.h @@ -16,14 +16,18 @@ //! \file DictWrapper.impl.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #include <functional> +#include <pybind11/pybind11.h> namespace walberla { namespace python_coupling { +namespace py = pybind11; + //=================================================================================================================== // @@ -33,17 +37,17 @@ namespace python_coupling { template<typename T> -void DictWrapper::exposePtr( const std::string & name, T * var ) { - this->d_[name] = boost::python::ptr( var ); +void DictWrapper::exposePtr(const char* name, T * var ) { + this->d_[name] = var; } template<typename T> -void DictWrapper::exposePtr( const std::string & name, const shared_ptr<T> & var ) { - this->d_[name] = boost::python::ptr( var.get() ); +void DictWrapper::exposePtr(const char* name, const shared_ptr<T> & var ) { + this->d_[name] = var.get(); } template<typename T> -void DictWrapper::exposeValue( const std::string & name, const T & var ) { +void DictWrapper::exposeValue( const char* name, const T & var ) { this->d_[name] = var; } @@ -57,21 +61,21 @@ void DictWrapper::exposeValue( const std::string & name, const T & var ) { template<typename T> -T DictWrapper::get( const std::string & name ) { - return boost::python::extract<T>( d_[name] ); +T DictWrapper::get( const char* name ) { + return py::cast<T>( d_[name] ); } template<typename T> -bool DictWrapper::has( const std::string & name ) +bool DictWrapper::has( const char* name ) { - if(! d_.has_key(name) ) + if(! d_.contains(name) ) return false; - return boost::python::extract<T>( d_[name]).check(); + return py::class_<T>( d_[name]).check(); } template<typename T> -bool DictWrapper::checkedGet( const std::string & name, T output ) +bool DictWrapper::checkedGet( const char* name, T output ) { if ( ! has<T>(name) ) return false; @@ -85,20 +89,20 @@ bool DictWrapper::checkedGet( const std::string & name, T output ) template<> -inline DictWrapper DictWrapper::get( const std::string & name ) { - auto dictCopy = boost::python::extract< boost::python::dict >( d_[name] ); +inline DictWrapper DictWrapper::get( const char* name ) { + auto dictCopy = py::dict( d_[name] ); DictWrapper result; result.dict() = dictCopy; return result; } template<> -inline bool DictWrapper::has<DictWrapper >( const std::string & name ) +inline bool DictWrapper::has<DictWrapper >( const char* name ) { - if(! d_.has_key(name) ) + if(! d_.contains(name) ) return false; - return boost::python::extract< boost::python::dict >( d_[name]).check(); + return py::isinstance<py::dict>(d_[name]); } @@ -109,22 +113,22 @@ inline bool DictWrapper::has<DictWrapper >( const std::string & name ) //=================================================================================================================== // void() -inline void runPythonObject( boost::python::object obj ) { +inline void runPythonObject( py::object obj ) { obj(); } template<> -inline std::function<void()> DictWrapper::get( const std::string & name ) { - boost::python::object obj ( d_[name] ); +inline std::function<void()> DictWrapper::get( const char* name ) { + py::object obj ( d_[name] ); return std::bind( &runPythonObject, obj ); } template<> -inline bool DictWrapper::has<std::function<void()> >( const std::string & name ) +inline bool DictWrapper::has<std::function<void()> >( const char* name ) { - if(! d_.has_key(name) ) + if(! d_.contains(name) ) return false; - return PyCallable_Check( boost::python::object(d_[name]).ptr() ) != 0; + return PyCallable_Check( py::object(d_[name]).ptr() ) != 0; } diff --git a/src/python_coupling/Manager.cpp b/src/python_coupling/Manager.cpp index b7af694e8..4b22df974 100644 --- a/src/python_coupling/Manager.cpp +++ b/src/python_coupling/Manager.cpp @@ -16,6 +16,7 @@ //! \file Manager.cpp //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -26,25 +27,23 @@ #ifdef WALBERLA_BUILD_WITH_PYTHON -#include "core/waLBerlaBuildInfo.h" -#include "Manager.h" -#include "core/logging/Logging.h" -#include "python_coupling/basic_exports/BasicExports.h" +# include "core/logging/Logging.h" +# include "core/waLBerlaBuildInfo.h" -#include <cstdlib> +# include "python_coupling/export/BasicExport.h" +# include <pybind11/embed.h> -BOOST_PYTHON_MODULE( walberla_cpp ) +# include "Manager.h" + +PYBIND11_MODULE( walberla_cpp, m) { using namespace walberla::python_coupling; - auto manager =Manager::instance(); - exportBasicWalberlaDatastructures(); - manager->exportAll(); + auto manager = Manager::instance(); + exportBasicWalberlaDatastructures(m); + manager->exportAll(m); } -using namespace boost::python; - - namespace walberla { namespace python_coupling { @@ -57,7 +56,7 @@ Manager::~Manager( ) //NOLINT { // To work reliably this would have to be called at the end of the // main function. At this position this leads to a segfault in some cases - // Py_Finalize(); + // py::finalize_interpreter(); } void Manager::addEntryToPythonPath( const std::string & path ) @@ -75,15 +74,13 @@ void Manager::addPath( const std::string & path ) { WALBERLA_ASSERT( initialized_ ); - object sys = import("sys"); - list sys_path = extract<list>( sys.attr("path") ); + py::object sys = py::module::import("sys"); + py::list sys_path = py::list( sys.attr("path") ); sys_path.append(path); } void Manager::triggerInitialization() { - using namespace boost::python; - if ( initialized_ ){ return; } @@ -91,15 +88,14 @@ void Manager::triggerInitialization() try { -#if PY_MAJOR_VERSION >= 3 + // The python module is used as embedded module here. There is a pybind11 macro for that called + // PYBIND11_EMBEDDED_MODULE. However it can not be used here since we want a shared lib for the python coupling. + // With the C-Call the so is embedded here. PyImport_AppendInittab( (char*)"walberla_cpp", PyInit_walberla_cpp ); -#else - PyImport_AppendInittab( (char*)"walberla_cpp", initwalberla_cpp ); -#endif - Py_Initialize(); - import("__main__"); - import("walberla_cpp"); + py::initialize_interpreter(); + py::module::import("__main__"); + py::module::import("walberla_cpp"); // Setup python path addPath( std::string(WALBERLA_SOURCE_DIR) + "/python" ); @@ -113,54 +109,41 @@ void Manager::triggerInitialization() entriesForPythonPath_.clear(); } - catch ( boost::python::error_already_set & ) { - PyObject *type_ptr = nullptr; - - PyObject *value_ptr = nullptr; - - PyObject *traceback_ptr = nullptr; - PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); - - if( type_ptr ) - { - extract<std::string> type_str(( str( handle<>( type_ptr ) ) )); - if( type_str.check() ) - WALBERLA_LOG_DEVEL( type_str() ); + catch ( py::error_already_set & e ) { + if (e.matches(PyExc_ModuleNotFoundError)){ + py::print("The module walberla_cpp could not be found"); } - if(value_ptr) - { - extract<std::string> value_str(( str( handle<>( value_ptr ) ) )); - if ( value_str.check() ) - WALBERLA_LOG_DEVEL( value_str() ); + else { + py::print("Unexpected Exception"); + throw; } - WALBERLA_ABORT( "Error while initializing Python" ); } } -void Manager::exportAll() +void Manager::exportAll(py::module_ &m) { for( auto it = exporterFunctions_.begin(); it != exporterFunctions_.end(); ++it ) { - (*it)(); + (*it)(m); } } -boost::python::object Manager::pythonObjectFromBlockData( IBlock & block, BlockDataID id ) +py::object Manager::pythonObjectFromBlockData( IBlock & block, BlockDataID id ) { - if( block.isDataOfType< boost::python::object > ( id ) ) - return *block.getData< boost::python::object > ( id ); + if( block.isDataOfType< py::object > ( id ) ){ + return *block.getData< py::object > ( id );} for( auto it = blockDataToObjectFunctions_.begin(); it != blockDataToObjectFunctions_.end(); ++it ) { auto res = (*it)( block, id ); - if ( res != boost::python::object() ) - return res; + if ( !res.is(py::object()) ){ + return res;} } - return boost::python::object(); + return py::object(); } diff --git a/src/python_coupling/Manager.h b/src/python_coupling/Manager.h index ed843ff11..8f860a27a 100644 --- a/src/python_coupling/Manager.h +++ b/src/python_coupling/Manager.h @@ -16,6 +16,7 @@ //! \file Manager.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> //! \brief Singleton managing Python coupling // //====================================================================================================================== @@ -27,11 +28,13 @@ #include "python_coupling/helper/MplHelpers.h" #include "core/singleton/Singleton.h" #include "domain_decomposition/IBlock.h" +#include <pybind11/pybind11.h> void init_module_walberla_cpp(); namespace walberla { namespace python_coupling { +namespace py = pybind11; class BlockDataToObjectTester @@ -44,24 +47,24 @@ namespace python_coupling { template<typename TypeToTest> void operator() ( NonCopyableWrap<TypeToTest> ) { - using boost::python::object; - if ( result_ == object() && block_->isDataClassOrSubclassOf<TypeToTest>( blockDataID_ ) ) { - result_ = object( boost::python::ptr( block_->getData<TypeToTest>( blockDataID_ ) ) ); + using py::object; + if ( result_.is(py::object()) && block_->isDataClassOrSubclassOf<TypeToTest>( blockDataID_ ) ) { + result_ = py::cast( block_->getData<TypeToTest>( blockDataID_ ) ); } } - boost::python::object getResult() { return result_; } + py::object getResult() { return result_; } private: IBlock * block_; BlockDataID blockDataID_; - boost::python::object result_; + py::object result_; }; - template<typename TypeList> - boost::python::object testBlockData( IBlock & block, BlockDataID blockDataID ) + template<typename... Types> + py::object testBlockData( IBlock & block, BlockDataID blockDataID ) { BlockDataToObjectTester tester( &block, blockDataID ); - for_each_noncopyable_type< TypeList > ( std::ref(tester) ); + for_each_noncopyable_type< Types... > ( std::ref(tester) ); return tester.getResult(); } @@ -71,8 +74,8 @@ namespace python_coupling { public: WALBERLA_BEFRIEND_SINGLETON; - typedef std::function<void()> ExporterFunction; - typedef std::function< boost::python::object ( IBlock&, BlockDataID ) > BlockDataToObjectFunction; + typedef std::function<void(py::module_&)> ExporterFunction; + typedef std::function< py::object ( IBlock&, BlockDataID ) > BlockDataToObjectFunction; ~Manager(); @@ -82,19 +85,21 @@ namespace python_coupling { void addEntryToPythonPath( const std::string & path ); - void addExporterFunction( const ExporterFunction & f ) { exporterFunctions_.push_back( f ); } - - template<typename TypeList> - void addBlockDataConversion() { blockDataToObjectFunctions_.push_back( &testBlockData<TypeList> ); } + void addExporterFunction( const ExporterFunction & f) + { + exporterFunctions_.push_back( f ); + } + template<typename... Types> + void addBlockDataConversion() { blockDataToObjectFunctions_.push_back( &testBlockData<Types...> ); } - protected: + void exportAll(py::module_ &m); + protected: void addPath( const std::string & path ); friend void ::init_module_walberla_cpp(); - void exportAll(); - friend boost::python::object IBlock_getData( boost::python::object, const std::string & ); - boost::python::object pythonObjectFromBlockData( IBlock & block, BlockDataID id ); + friend py::object IBlock_getData( py::object, const std::string & ); + py::object pythonObjectFromBlockData( IBlock & block, BlockDataID id ); Manager(); diff --git a/src/python_coupling/PythonCallback.cpp b/src/python_coupling/PythonCallback.cpp index 507087fc9..b4d0fb6d8 100644 --- a/src/python_coupling/PythonCallback.cpp +++ b/src/python_coupling/PythonCallback.cpp @@ -16,36 +16,30 @@ //! \file PythonCallback.cpp //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #include "PythonCallback.h" -#include "PythonWrapper.h" #include "DictWrapper.h" #ifdef WALBERLA_BUILD_WITH_PYTHON #include "Manager.h" #include "core/Abort.h" -#include "core/logging/Logging.h" -#include "helper/ExceptionHandling.h" #include "core/Filesystem.h" -#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20160609 -#define CURRENT_PATH_WORKAROUND -#include <unistd.h> -#include <errno.h> -#endif +#include "pybind11/eval.h" namespace walberla { namespace python_coupling { - static boost::python::object importModuleOrFileInternal( const std::string & fileOrModuleName, const std::vector< std::string > & argv ) + static py::object importModuleOrFileInternal( const std::string & fileOrModuleName, const std::vector< std::string > & argv ) { auto manager = python_coupling::Manager::instance(); manager->triggerInitialization(); - namespace bp = boost::python; + namespace py = pybind11; std::string moduleName = fileOrModuleName; @@ -57,52 +51,25 @@ namespace python_coupling { code << "] \n"; filesystem::path path ( fileOrModuleName ); -#ifdef CURRENT_PATH_WORKAROUND - // workaround for double free in filesystem::current_path in libstdc++ 5.4 and lower - size_t cwd_size = 16; - char * cwd_buf = (char*) std::malloc(cwd_size * sizeof(char)); - - while( getcwd( cwd_buf, cwd_size ) == NULL ) - { - if (errno == ERANGE) - { - cwd_size *= 2; - cwd_buf = (char*) std::realloc( cwd_buf, cwd_size * sizeof(char) ); - } - else - { - python_coupling::terminateOnPythonException( std::string("Could not determine working directory") ); - } - } - - std::string cwd(cwd_buf); - std::free(cwd_buf); - path = filesystem::absolute( path, cwd ); -#else path = filesystem::absolute( path ); -#endif + if ( path.extension() == ".py" ) { moduleName = path.stem().string(); - - - if ( ! path.parent_path().empty() ) { -#ifdef CURRENT_PATH_WORKAROUND - std::string p = filesystem::canonical(path.parent_path(), cwd).string(); -#else + if ( ! path.parent_path().empty() ) { std::string p = filesystem::canonical(path.parent_path()).string(); -#endif code << "sys.path.append( r'" << p << "')" << "\n"; } } - bp::exec( code.str().c_str(), bp::import("__main__").attr("__dict__") ); + + py::exec( code.str().c_str(), py::module::import("__main__").attr("__dict__") ); try { - return bp::import( moduleName.c_str() ); + return py::module::import( moduleName.c_str() ); } - catch ( bp::error_already_set & ) { - python_coupling::terminateOnPythonException( std::string("Python Error while loading ") + fileOrModuleName ); - return boost::python::object(); + catch ( py::error_already_set &e) { + throw py::value_error(e.what()); + return py::none(); } } @@ -116,7 +83,7 @@ namespace python_coupling { : exposedVars_( new DictWrapper() ), callbackDict_( new DictWrapper() ) { Manager::instance()->triggerInitialization(); - callbackDict_->dict() = boost::python::dict(); + callbackDict_->dict() = py::dict(); } @@ -125,13 +92,13 @@ namespace python_coupling { { Manager::instance()->triggerInitialization(); - using namespace boost::python; + namespace py = pybind11; // Add empty callbacks module importModuleOrFileInternal( fileOrModuleName, argv ); - object callbackModule = import( "walberla_cpp.callbacks"); + py::object callbackModule = py::module::import( "walberla_cpp.callbacks"); - callbackDict_->dict() = extract<dict>( callbackModule.attr( "__dict__" ) ); + callbackDict_->dict() = py::dict( callbackModule.attr( "__dict__" ) ); } @@ -140,41 +107,36 @@ namespace python_coupling { { Manager::instance()->triggerInitialization(); - using namespace boost::python; - // Add empty callbacks module - object callbackModule = import( "walberla_cpp.callbacks"); + py::object callbackModule = py::module::import( "walberla_cpp.callbacks"); - callbackDict_->dict() = extract<dict>( callbackModule.attr( "__dict__" ) ); + callbackDict_->dict() = py::dict( callbackModule.attr( "__dict__" ) ); } bool PythonCallback::isCallable() const { - return callbackDict_->dict().has_key( functionName_ ); + return callbackDict_->dict().contains( functionName_ ); } void PythonCallback::operator() () { if ( ! isCallable() ) WALBERLA_ABORT_NO_DEBUG_INFO( "Could not call python function '" << functionName_ << "'. " << - "Did you forget to set the callback function?" ); + "Did you forget to set the callback function?" ); - namespace bp = boost::python; + namespace py = pybind11; try { - if ( exposedVars_->dict().has_key("returnValue")) - bp::api::delitem( exposedVars_->dict(), "returnValue" ); - - bp::object function = callbackDict_->dict()[ functionName_ ]; + py::object function = callbackDict_->dict()[ functionName_.c_str() ]; - bp::object returnVal; - returnVal = function( *bp::tuple(), **(exposedVars_->dict() ) ); + py::object returnVal; + returnVal = function( *py::tuple(), **(exposedVars_->dict() ) ); exposedVars_->dict()["returnValue"] = returnVal; } - catch ( bp::error_already_set & ) { - python_coupling::terminateOnPythonException( std::string("Error while running Python function ") + functionName_ ); + catch ( py::error_already_set &e ) { + throw py::value_error(e.what()); } } diff --git a/src/python_coupling/PythonCallback.h b/src/python_coupling/PythonCallback.h index de7d0eaee..9eeaec650 100644 --- a/src/python_coupling/PythonCallback.h +++ b/src/python_coupling/PythonCallback.h @@ -16,6 +16,7 @@ //! \file PythonCallback.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -79,8 +80,8 @@ namespace python_coupling { PythonCallback(); PythonCallback( const std::string & functionName ); PythonCallback( const std::string & moduleOrFile, - const std::string & functionName, - const std::vector<std::string> & argv = std::vector<std::string>() ); + const std::string & functionName, + const std::vector<std::string> & argv = std::vector<std::string>() ); DictWrapper & data() { return *exposedVars_; } const DictWrapper & data() const { return *exposedVars_; } diff --git a/src/python_coupling/PythonWrapper.h b/src/python_coupling/PythonWrapper.h index 74ce9918e..7ae14bada 100644 --- a/src/python_coupling/PythonWrapper.h +++ b/src/python_coupling/PythonWrapper.h @@ -17,6 +17,7 @@ //! \ingroup core //! \author Matthias Markl <matthias.markl@fau.de> //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -36,9 +37,7 @@ #endif #endif -#include <boost/python.hpp> -#include <boost/python/stl_iterator.hpp> -#include <boost/python/suite/indexing/vector_indexing_suite.hpp> +#include "pybind11/pybind11.h" #ifdef _MSC_VER #ifdef __CREATED_HAVE_ROUND diff --git a/src/python_coupling/Shell.cpp b/src/python_coupling/Shell.cpp deleted file mode 100644 index 5749511e0..000000000 --- a/src/python_coupling/Shell.cpp +++ /dev/null @@ -1,247 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Shell.impl.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important - -#include "PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "Shell.h" -#include "DictWrapper.h" -#include "Manager.h" - -#include "core/logging/Logging.h" -#include "core/StringUtility.h" - - -namespace walberla { -namespace python_coupling { - - - Shell::Shell( const std::string & prompt ) - : exposedVars_( new DictWrapper() ) - { - Manager::instance()->triggerInitialization(); - - using namespace boost::python; - - try { - object main_module = import("__main__"); - dict globals = extract<dict>( main_module.attr( "__dict__" ) ); - - exec("import os \n" - "import code \n" - "try:\n" - " import readline \n" - " import rlcompleter \n" - " readline.parse_and_bind(\"tab: complete\") \n" - " histfile = os.path.join( os.path.expanduser('~'), '.waLBerla_history') \n" - " try:\n" - " readline.read_history_file(histfile)\n" - " except IOError:\n" - " pass\n" - "except ImportError:\n" - " pass\n" - "\n", globals ); - - - prompt1_ = prompt; - for ( uint_t i=0; i < prompt.size(); ++i ) - prompt2_ += " "; - - prompt1_ += "> "; - prompt2_ += "> "; - - exec("import sys", globals ); - exec( std::string( "sys.ps1 = '" + prompt1_ + "'" ).c_str(), globals ); - exec( std::string( "sys.ps2 = '" + prompt2_ + "'" ).c_str(), globals ); - } - catch ( error_already_set & ) { - PyErr_Print(); - WALBERLA_ABORT( "Error initializing Shell" ); - } - } - - Shell::~Shell() - { - using namespace boost::python; - - object main_module = import("__main__"); - dict globals = extract<dict>( main_module.attr( "__dict__" ) ); - exec("readline.write_history_file(histfile)", globals ); - } - - - bool Shell::isCompleteCommand ( const std::string & code ) - { - using namespace boost::python; - object main_module = import("__main__"); - dict globals = extract<dict>( main_module.attr( "__dict__" ) ); - - boost::python::dict locals; - locals["codeToTest"] = code; - object compiledCode = eval( str( "code.compile_command(codeToTest)" ), globals, locals ); - return ( compiledCode != object() ); - } - - bool Shell::getCompleteCommand( std::string & result ) - { - uint_t lineCounter = 0; - - while ( true ) - { - char* line; - - if ( lineCounter == 0 ) - line = PyOS_Readline( stdin, stdout, (char*)prompt1_.c_str() ); - else - line = PyOS_Readline( stdin, stdout, (char*)prompt2_.c_str() ); - - if ( line == nullptr || *line == '\0' ) { // interrupt or EOF - result.clear(); - return false; - } - - std::string strLine ( line ); - std::string strTrimmedLine( line ); - string_trim( strTrimmedLine ); - - PyMem_Free( line ); - - lineCounter++; - result += strLine; - - bool commandComplete = isCompleteCommand( result ); - - if ( lineCounter == 1 && commandComplete ) - return true; - if ( strTrimmedLine.empty() && isCompleteCommand(result) ) // multiline commands have to end with empty line - return true; - } - - return false; - } - - - void Shell::operator() () - { - using namespace boost::python; - - object main_module = import("__main__"); - dict globals = extract<dict>( main_module.attr( "__dict__" ) ); - - globals.update( exposedVars_->dict() ); - exec( "from waLBerla import *", globals ); - - - const int MAX_LINE_LENGTH = 1024; - const char continueMarker = 'c'; - const char stopMarker = 's'; - - WALBERLA_ROOT_SECTION() - { - std::string code; - - while ( true ) - { - code.clear(); - - try - { - if ( ! getCompleteCommand(code ) ) { - std::cout << "\n"; - code.resize( MAX_LINE_LENGTH ); - code[0] = stopMarker; - MPI_Bcast( (void*) code.c_str(), MAX_LINE_LENGTH, // send stop command - MPI_CHAR, 0, MPI_COMM_WORLD ); - break; - } - else - { - std::string codeToSend = continueMarker + code; - - if ( codeToSend.size() >= uint_c( MAX_LINE_LENGTH ) ) - { - WALBERLA_LOG_WARNING("Line length too big, only allowed " << MAX_LINE_LENGTH-1 << " characters" ); - continue; - } - codeToSend.resize( MAX_LINE_LENGTH ); - MPI_Bcast( (void*) codeToSend.c_str(), MAX_LINE_LENGTH, // send code snippet to other processes - MPI_CHAR, 0, MPI_COMM_WORLD ); - - PyRun_SimpleString( code.c_str() ); - fflush( stderr ); - WALBERLA_MPI_BARRIER(); - } - } - catch( boost::python::error_already_set & ) { - PyErr_Print(); - } - } - } - else - { - char * buffer = new char[MAX_LINE_LENGTH]; - - while ( true) - { - - MPI_Bcast( (void*) buffer, MAX_LINE_LENGTH, // send code snippet to other processes - MPI_CHAR, 0, MPI_COMM_WORLD ); - - if ( *buffer == stopMarker ) - break; - - std::string code ( buffer+1 ); - - PyRun_SimpleString( code.c_str() ); - fflush( stderr ); - WALBERLA_MPI_BARRIER(); - } - - - delete [] buffer; - } - } - - -} // namespace python_coupling -} // namespace walberla - - -#else - - -#include "Shell.h" - -namespace walberla { -namespace python_coupling { - - Shell::Shell( const std::string & ) {} - Shell::~Shell() = default; - void Shell::operator()() {} - -} // namespace python_coupling -} // namespace walberla - - -#endif diff --git a/src/python_coupling/Shell.h b/src/python_coupling/Shell.h deleted file mode 100644 index 41be5c3c8..000000000 --- a/src/python_coupling/Shell.h +++ /dev/null @@ -1,63 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ScriptRunner.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -//! \brief Runs a python script that can access waLBerla variables -// -//====================================================================================================================== - -#pragma once - -#include <string> - -#include "waLBerlaDefinitions.h" -#include "core/DataTypes.h" - - - -namespace walberla { -namespace python_coupling { - - class DictWrapper; - - class Shell - { - public: - Shell( const std::string & prompt = "waLBerla"); - ~Shell(); - - void operator() (); - - inline void run() { (*this)(); } - inline DictWrapper & data() { return *exposedVars_; } - - protected: - bool getCompleteCommand( std::string & result ); - bool isCompleteCommand ( const std::string & code ); - - shared_ptr<DictWrapper> exposedVars_; - std::string prompt1_; - std::string prompt2_; - }; - - -} // namespace python_coupling -} // namespace walberla - - - - diff --git a/src/python_coupling/TimeloopIntercept.cpp b/src/python_coupling/TimeloopIntercept.cpp deleted file mode 100644 index 17e7df788..000000000 --- a/src/python_coupling/TimeloopIntercept.cpp +++ /dev/null @@ -1,136 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file TimeloopIntercept.cpp -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#include "TimeloopIntercept.h" -#include "DictWrapper.h" -#include "PythonCallback.h" -#include "Shell.h" - -#include "core/logging/Logging.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - - - -#if defined(_MSC_VER) - -#else -# include <csignal> -#endif - - -static bool signalTriggered = false; - - -namespace walberla { -namespace python_coupling { - - -#if defined(_MSC_VER) - -void enableSignalHandler() { - -} - -#else - -void customSignalHandler( int ) { - signalTriggered = true; -} - -void enableSignalHandler() { - std::signal (SIGUSR1, customSignalHandler ); -} - -#endif - - - -TimeloopIntercept::TimeloopIntercept( const std::string & functionName, uint_t interval, bool enableSignalInterrupt ) - : callback_ ( walberla::make_shared<PythonCallback> ( functionName ) ), - timestep_ ( 0 ), - interval_ ( interval ), - enableSignalInterrupt_ ( enableSignalInterrupt ) -{ - if ( enableSignalInterrupt ) - enableSignalHandler(); -} - -TimeloopIntercept::TimeloopIntercept( const std::string & pythonFile, const std::string & functionName, - uint_t interval, bool enableSignalInterrupt ) - : callback_ ( walberla::make_shared<PythonCallback> ( pythonFile, functionName ) ), - timestep_ ( 0 ), - interval_ ( interval ), - enableSignalInterrupt_ ( enableSignalInterrupt ) -{ - if ( enableSignalInterrupt ) - enableSignalHandler(); -} - - -void TimeloopIntercept::operator() () -{ - ++timestep_; - if( interval_ > 0 && ( timestep_ % interval_ == 0 ) ) - (*callback_)(); - - if ( enableSignalInterrupt_ && signalTriggered ) - { - WALBERLA_LOG_INFO( "Interrupting Simulation at timestep " << timestep_ ); - signalTriggered = false; - Shell shell; - shell.data() = this->callback()->data(); - shell.run(); - WALBERLA_LOG_INFO( "Continue Simulation at timestep " << timestep_+1 << " ..." ); - } -} - - - -} // namespace python_coupling -} // namespace walberla - -#else - -namespace walberla { -namespace python_coupling { - -TimeloopIntercept::TimeloopIntercept( const std::string & functionName, uint_t interval, bool enableSignalInterrupt ) - : callback_ ( walberla::make_shared<PythonCallback> ( functionName ) ), - timestep_ ( 0 ), - interval_ ( interval ), - enableSignalInterrupt_ ( enableSignalInterrupt ) -{} - -TimeloopIntercept::TimeloopIntercept( const std::string & pythonFile, const std::string & functionName, - uint_t interval, bool enableSignalInterrupt ) - : callback_ ( walberla::make_shared<PythonCallback> ( pythonFile, functionName ) ), - timestep_ ( 0 ), - interval_ ( interval ), - enableSignalInterrupt_ ( enableSignalInterrupt ) -{} - -void TimeloopIntercept::operator() (){} - -} // namespace python_coupling -} // namespace walberla - -#endif diff --git a/src/python_coupling/TimeloopIntercept.h b/src/python_coupling/TimeloopIntercept.h deleted file mode 100644 index fc7f49645..000000000 --- a/src/python_coupling/TimeloopIntercept.h +++ /dev/null @@ -1,60 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ScriptRunner.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "core/DataTypes.h" - -namespace walberla { -namespace python_coupling { - - class DictWrapper; - class PythonCallback; - - - class TimeloopIntercept - { - public: - TimeloopIntercept( const std::string & functionName, - uint_t interval = 1, bool enableSignalInterrupt=true ); - - TimeloopIntercept( const std::string & pythonFile, const std::string & functionName, - uint_t interval = 1, bool enableSignalInterrupt=true ); - - inline shared_ptr<PythonCallback> callback() { return callback_; } - - void operator() (); - - protected: - shared_ptr<PythonCallback> callback_; - uint_t timestep_; - uint_t interval_; - bool enableSignalInterrupt_; - }; - - -} // namespace python_coupling -} // namespace walberla - - - - - diff --git a/src/python_coupling/all.h b/src/python_coupling/all.h deleted file mode 100644 index 9c16b51e1..000000000 --- a/src/python_coupling/all.h +++ /dev/null @@ -1,32 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file all.h -//! \ingroup python_coupling -//! \author Christian Godenschwager <christian.godenschwager@fau.de> -//! \brief Collective header file for module python_coupling -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - #include "PythonCallback.h" - #include "CreateConfig.h" - #include "Shell.h" - #include "TimeloopIntercept.h" -#endif diff --git a/src/python_coupling/basic_exports/BasicExports.cpp b/src/python_coupling/basic_exports/BasicExports.cpp deleted file mode 100644 index 2d180f58f..000000000 --- a/src/python_coupling/basic_exports/BasicExports.cpp +++ /dev/null @@ -1,1238 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file BasicExports.cpp -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "BasicExports.h" -#include "MPIExport.h" -#include "python_coupling/helper/ModuleScope.h" -#include "core/waLBerlaBuildInfo.h" -#include "core/logging/Logging.h" -#include "core/Abort.h" -#include "core/cell/CellInterval.h" -#include "core/math/AABB.h" -#include "core/mpi/MPIIO.h" -#include "core/timing/ReduceType.h" -#include "core/timing/TimingPool.h" -#include "core/timing/TimingTree.h" -#include "communication/UniformPackInfo.h" -#include "communication/UniformMPIDatatypeInfo.h" -#include "domain_decomposition/StructuredBlockStorage.h" -#include "python_coupling/Manager.h" -#include "python_coupling/helper/BlockStorageExportHelpers.h" -#include "stencil/Directions.h" - -#include <boost/version.hpp> - -#include <functional> - -using namespace boost::python; - - -namespace walberla { -namespace python_coupling { - - -template <class T> -struct NumpyIntConversion -{ - NumpyIntConversion() - { - converter::registry::push_back( &convertible, &construct, boost::python::type_id<T>() ); - } - - static void* convertible( PyObject* pyObj) - { - auto typeName = std::string( Py_TYPE(pyObj)->tp_name ); - if ( typeName.substr(0,9) == "numpy.int" ) - return pyObj; - return nullptr; - } - - static void construct( PyObject* pyObj, converter::rvalue_from_python_stage1_data* data ) - { - handle<> x(borrowed(pyObj)); - object o(x); - T value = extract<T>(o.attr("__int__")()); - void* storage =( (boost::python::converter::rvalue_from_python_storage<T>*) data)->storage.bytes; - new (storage) T(value); - data->convertible = storage; - } -}; - -template <class T> -struct NumpyFloatConversion -{ - NumpyFloatConversion() - { - converter::registry::push_back( &convertible, &construct, boost::python::type_id<T>() ); - } - - static void* convertible(PyObject* pyObj) - { - auto typeName = std::string( Py_TYPE(pyObj)->tp_name ); - if ( typeName.substr(0,11) == "numpy.float" ) - return pyObj; - return nullptr; - } - - static void construct(PyObject* pyObj, converter::rvalue_from_python_stage1_data* data) - { - handle<> x(borrowed(pyObj)); - object o(x); -#ifdef WALBERLA_CXX_COMPILER_IS_GNU -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif - T value = extract<T>(o.attr("__float__")()); -#ifdef WALBERLA_CXX_COMPILER_IS_GNU -#pragma GCC diagnostic pop -#endif - void* storage =( (boost::python::converter::rvalue_from_python_storage<T>*) data)->storage.bytes; - new (storage) T(value); - data->convertible = storage; - } -}; - - -#if BOOST_VERSION < 106300 -// taken from https://github.com/boostorg/python/commit/97e4b34a15978ca9d7c296da2de89b78bba4e0d5 -template <class T> -struct exportSharedPtr -{ - exportSharedPtr() - { - converter::registry::insert( &convertible, &construct, boost::python::type_id<std::shared_ptr<T> >() -#ifndef BOOST_PYTHON_NO_PY_SIGNATURES - , &converter::expected_from_python_type_direct<T>::get_pytype -#endif - ); - } - -private: - static void* convertible( PyObject* p ) - { - if ( p == Py_None ) - return p; - - return converter::get_lvalue_from_python( p, converter::registered<T>::converters ); - } - - static void construct( PyObject* source, converter::rvalue_from_python_stage1_data* data ) - { - void* const storage = ( (converter::rvalue_from_python_storage< std::shared_ptr<T> >*) data )->storage.bytes; - // Deal with the "None" case. - if ( data->convertible == source ) - new (storage) std::shared_ptr<T>(); - else - { - std::shared_ptr<void> hold_convertible_ref_count( (void*)0, converter::shared_ptr_deleter( handle<>( borrowed( source ) ) ) ); - // use aliasing constructor - new (storage) std::shared_ptr<T>( hold_convertible_ref_count, static_cast<T*>(data->convertible) ); - } - - data->convertible = storage; - } -}; -#endif - -//====================================================================================================================== -// -// Helper Functions -// -//====================================================================================================================== - -void checkForThreeSequence( const object & o, const char * message ) -{ - // strange construct required because also the len function throws , if object has no len - try { - if ( len(o ) != 3 ) throw error_already_set(); - } - catch( error_already_set & ) { - PyErr_SetString(PyExc_RuntimeError, message); - throw error_already_set(); - } -} - -//====================================================================================================================== -// -// Vector3 -// -//====================================================================================================================== - - -template<typename T> -struct Vector3_to_PythonTuple -{ - static PyObject* convert( Vector3<T> const& v ) - { - auto resultTuple = boost::python::make_tuple(v[0], v[1], v[2] ); - return boost::python::incref ( boost::python::object ( resultTuple ).ptr () ); - } -}; - -template<typename T> -struct PythonTuple_to_Vector3 -{ - PythonTuple_to_Vector3() - { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id<Vector3<T> >()); - } - - static void* convertible(PyObject* obj) - { - using namespace boost::python; - - if ( ! ( PySequence_Check(obj) && PySequence_Size( obj ) == 3 )) - return nullptr; - - object element0 ( handle<>( borrowed( PySequence_GetItem(obj,0) ))); - object element1 ( handle<>( borrowed( PySequence_GetItem(obj,1) ))); - object element2 ( handle<>( borrowed( PySequence_GetItem(obj,2) ))); - - if ( extract<T>( element0 ).check() && - extract<T>( element1 ).check() && - extract<T>( element2 ).check() ) - return obj; - else - return nullptr; - } - - static void construct( PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data ) - { - using namespace boost::python; - - object element0 ( handle<>( borrowed( PySequence_GetItem(obj,0) ))); - object element1 ( handle<>( borrowed( PySequence_GetItem(obj,1) ))); - object element2 ( handle<>( borrowed( PySequence_GetItem(obj,2) ))); - - - // Grab pointer to memory into which to construct the new Vector3 - void* storage = ( (boost::python::converter::rvalue_from_python_storage<Vector3<T> >*) data )->storage.bytes; - - new (storage) Vector3<T> ( extract<T>( element0 ), - extract<T>( element1 ), - extract<T>( element2 ) ); - - // Stash the memory chunk pointer for later use by boost.python - data->convertible = storage; - } -}; - -void exportVector3() -{ - // To Python - boost::python::to_python_converter< Vector3<bool >, Vector3_to_PythonTuple<bool > >(); - - boost::python::to_python_converter< Vector3<real_t >, Vector3_to_PythonTuple<real_t > >(); - - boost::python::to_python_converter< Vector3<uint8_t >, Vector3_to_PythonTuple<uint8_t > >(); - boost::python::to_python_converter< Vector3<uint16_t >, Vector3_to_PythonTuple<uint16_t > >(); - boost::python::to_python_converter< Vector3<uint32_t >, Vector3_to_PythonTuple<uint32_t > >(); - boost::python::to_python_converter< Vector3<uint64_t >, Vector3_to_PythonTuple<uint64_t > >(); - - boost::python::to_python_converter< Vector3<cell_idx_t>, Vector3_to_PythonTuple<cell_idx_t> >(); - - // From Python - PythonTuple_to_Vector3<bool >(); - - PythonTuple_to_Vector3<real_t >(); - - PythonTuple_to_Vector3<uint8_t >(); - PythonTuple_to_Vector3<uint16_t >(); - PythonTuple_to_Vector3<uint32_t >(); - PythonTuple_to_Vector3<uint64_t >(); - - PythonTuple_to_Vector3<cell_idx_t>(); -} - - -//====================================================================================================================== -// -// Cell -// -//====================================================================================================================== - - -struct Cell_to_PythonTuple -{ - static PyObject* convert( Cell const& c ) - { - auto resultTuple = boost::python::make_tuple(c[0], c[1], c[2] ); - return boost::python::incref ( boost::python::object ( resultTuple ).ptr () ); - } -}; - -struct PythonTuple_to_Cell -{ - PythonTuple_to_Cell() - { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id< Cell >()); - } - - static void* convertible( PyObject* obj ) - { - using namespace boost::python; - - if ( ! ( PySequence_Check(obj) && PySequence_Size( obj ) == 3 )) - return nullptr; - - object element0 ( handle<>( borrowed( PySequence_GetItem(obj,0) ))); - object element1 ( handle<>( borrowed( PySequence_GetItem(obj,1) ))); - object element2 ( handle<>( borrowed( PySequence_GetItem(obj,2) ))); - - if ( extract<cell_idx_t>( element0 ).check() && - extract<cell_idx_t>( element1 ).check() && - extract<cell_idx_t>( element2 ).check() ) - return obj; - else - return nullptr; - } - - static void construct( PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data ) - { - using namespace boost::python; - - object element0 ( handle<>( borrowed( PySequence_GetItem(obj,0) ))); - object element1 ( handle<>( borrowed( PySequence_GetItem(obj,1) ))); - object element2 ( handle<>( borrowed( PySequence_GetItem(obj,2) ))); - - - // Grab pointer to memory into which to construct the new Vector3 - void* storage = ( (boost::python::converter::rvalue_from_python_storage<Cell>*) data )->storage.bytes; - - new (storage) Cell( extract<cell_idx_t>( element0 ), - extract<cell_idx_t>( element1 ), - extract<cell_idx_t>( element2 ) ); - - // Stash the memory chunk pointer for later use by boost.python - data->convertible = storage; - } -}; - -void exportCell() -{ - // To Python - boost::python::to_python_converter< Cell, Cell_to_PythonTuple >(); - // From Python - PythonTuple_to_Cell(); -} - -//====================================================================================================================== -// -// CellInterval -// -//====================================================================================================================== - - -void cellInterval_setMin( CellInterval & ci, const Cell & min ) { - ci.min() = min; -} -void cellInterval_setMax( CellInterval & ci, const Cell & max ) { - ci.max() = max; -} -void cellInterval_shift( CellInterval & ci, cell_idx_t xShift, cell_idx_t yShift, cell_idx_t zShift ) { - ci.shift( xShift, yShift, zShift ); -} - -boost::python::tuple cellInterval_size( CellInterval & ci ) { - return boost::python::make_tuple( ci.xSize(), ci.ySize(), ci.zSize() ); -} - -CellInterval cellInterval_getIntersection( CellInterval & ci1, CellInterval & ci2 ) -{ - CellInterval result ( ci1 ); - result.intersect( ci2 ); - return result; -} - -CellInterval cellInterval_getShifted( CellInterval & ci1, cell_idx_t xShift, cell_idx_t yShift, cell_idx_t zShift ) -{ - CellInterval result ( ci1 ); - result.shift( xShift, yShift, zShift ); - return result; -} - -CellInterval cellInterval_getExpanded1( CellInterval & ci1, cell_idx_t expandVal ) -{ - CellInterval result ( ci1 ); - result.expand( expandVal ); - return result; -} - -CellInterval cellInterval_getExpanded2( CellInterval & ci1, cell_idx_t xExpand, cell_idx_t yExpand, cell_idx_t zExpand ) -{ - CellInterval result ( ci1 ); - result.expand( Cell(xExpand, yExpand, zExpand) ); - return result; -} - -void exportCellInterval() -{ - const Cell & ( CellInterval::*p_getMin )( ) const = &CellInterval::min; - const Cell & ( CellInterval::*p_getMax )( ) const = &CellInterval::max; - - bool ( CellInterval::*p_contains1) ( const Cell & ) const = &CellInterval::contains; - bool ( CellInterval::*p_contains2) ( const CellInterval & ) const = &CellInterval::contains; - - void ( CellInterval::*p_expand1) ( const cell_idx_t ) = &CellInterval::expand; - void ( CellInterval::*p_expand2) ( const Cell & ) = &CellInterval::expand; - - bool ( CellInterval::*p_overlaps ) ( const CellInterval & ) const = &CellInterval::overlaps; - - class_<CellInterval>("CellInterval") - .def( init<const Cell&, const Cell&>() ) - .def( init<cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t>() ) - .add_property( "min", make_function( p_getMin, return_value_policy<copy_const_reference>() ), &cellInterval_setMin ) - .add_property( "max", make_function( p_getMax, return_value_policy<copy_const_reference>() ), &cellInterval_setMax ) - .add_property( "size", &cellInterval_size ) - .def( "empty", &CellInterval::empty ) - .def( "positiveIndicesOnly", &CellInterval::positiveIndicesOnly ) - .def( "contains", p_contains1 ) - .def( "contains", p_contains2 ) - .def( "overlaps", p_overlaps ) - .def( "shift", &cellInterval_shift ) - .def( "getShifted", &cellInterval_getShifted ) - .def( "expand", p_expand1 ) - .def( "expand", p_expand2 ) - .def( "getExpanded", &cellInterval_getExpanded1 ) - .def( "getExpanded", &cellInterval_getExpanded2 ) - .def( "intersect", &CellInterval::intersect ) - .def( "getIntersection", &cellInterval_getIntersection ) - .def("__eq__", &CellInterval::operator==) - .def("__ne__", &CellInterval::operator!=) - .add_property( "numCells", &CellInterval::numCells ) - .def( self_ns::str(self) ) - ; -} - -//====================================================================================================================== -// -// AABB -// -//====================================================================================================================== - - -tuple aabb_getMin( const AABB & domainBB ) { - return boost::python::make_tuple( domainBB.xMin(), domainBB.yMin(), domainBB.zMin() ); -} -tuple aabb_getMax( const AABB & domainBB ) { - return boost::python::make_tuple( domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); -} - -void aabb_setMin( AABB & domainBB, object min ) -{ - checkForThreeSequence(min, "Error assigning minimum of AABB - Sequence of length 3 required" ); - real_t min0 = extract<real_t>( min[0] ); - real_t min1 = extract<real_t>( min[1] ); - real_t min2 = extract<real_t>( min[2] ); - - domainBB = AABB( domainBB.max(), AABB::vector_type ( min0, min1, min2 ) ); -} - -void aabb_setMax( AABB & domainBB, object max ) -{ - checkForThreeSequence( max, "Error assigning maximum of AABB - Sequence of length 3 required" ); - - real_t max0 = extract<real_t>( max[0] ); - real_t max1 = extract<real_t>( max[1] ); - real_t max2 = extract<real_t>( max[2] ); - - domainBB = AABB( domainBB.min(), AABB::vector_type ( max0, max1, max2 ) ); -} - - -void exportAABB() -{ - bool ( AABB::*p_containsBB )( const AABB & bb ) const = &AABB::contains; - bool ( AABB::*p_containsVec )( const Vector3<real_t> & point ) const = &AABB::contains; - - - bool ( AABB::*p_containsClosedInterval1 ) ( const Vector3<real_t> & ) const = &AABB::containsClosedInterval; - bool ( AABB::*p_containsClosedInterval2 ) ( const Vector3<real_t> &, const real_t ) const = &AABB::containsClosedInterval; - - AABB ( AABB::*p_getExtended1 ) ( const real_t ) const = &AABB::getExtended; - AABB ( AABB::*p_getExtended2 ) ( const Vector3<real_t> & ) const = &AABB::getExtended; - - AABB ( AABB::*p_getScaled1 ) ( const real_t ) const = &AABB::getScaled; - AABB ( AABB::*p_getScaled2 ) ( const Vector3<real_t> & ) const = &AABB::getScaled; - - AABB ( AABB::*p_getMerged1 ) ( const Vector3<real_t> & ) const = &AABB::getMerged; - AABB ( AABB::*p_getMerged2 ) ( const AABB & ) const = &AABB::getMerged; - - bool ( AABB::*p_intersects1 )( const AABB & bb ) const = &AABB::intersects; - bool ( AABB::*p_intersects2 )( const AABB & bb, real_t dx ) const = &AABB::intersects; - - bool ( AABB::*p_intersectsClosed1 )( const AABB & bb ) const = &AABB::intersectsClosedInterval; - bool ( AABB::*p_intersectsClosed2 )( const AABB & bb, real_t dx ) const = &AABB::intersectsClosedInterval; - - void ( AABB::*p_extend1 ) ( const real_t ) = &AABB::extend; - void ( AABB::*p_extend2 ) ( const Vector3<real_t> & ) = &AABB::extend; - - void ( AABB::*p_scale1 ) ( const real_t ) = &AABB::scale; - void ( AABB::*p_scale2 ) ( const Vector3<real_t> & ) = &AABB::scale; - - void ( AABB::*p_merge1 ) ( const Vector3<real_t> & ) = &AABB::merge; - void ( AABB::*p_merge2 ) ( const AABB & ) = &AABB::merge; - - real_t ( AABB::*p_sqDistance1 ) ( const AABB & ) const = &AABB::sqDistance; - real_t ( AABB::*p_sqDistance2 ) ( const Vector3<real_t> & ) const = &AABB::sqDistance; - - real_t ( AABB::*p_sqMaxDistance1 ) ( const AABB & ) const = &AABB::sqMaxDistance; - real_t ( AABB::*p_sqMaxDistance2 ) ( const Vector3<real_t> & ) const = &AABB::sqMaxDistance; - - - class_<AABB>("AABB") - .def( init<real_t,real_t,real_t,real_t,real_t,real_t>() ) - .def( init<Vector3<real_t>,Vector3<real_t> >() ) - .def("__eq__", &walberla::math::operator==<real_t, real_t > ) - .def("__ne__", &walberla::math::operator!=<real_t, real_t > ) - .add_property( "min", &aabb_getMin, &aabb_setMin ) - .add_property( "max", &aabb_getMax, &aabb_setMax ) - .add_property( "size", &AABB::sizes ) - .def( "empty", &AABB::empty ) - .def( "volume", &AABB::volume ) - .def( "center", &AABB::center ) - .def( "contains", p_containsBB ) - .def( "contains", p_containsVec ) - .def( "containsClosedInterval", p_containsClosedInterval1 ) - .def( "containsClosedInterval", p_containsClosedInterval2 ) - .def( "getExtended", p_getExtended1 ) - .def( "getExtended", p_getExtended2 ) - .def( "getTranslated", &AABB::getTranslated ) - .def( "getScaled", p_getScaled1 ) - .def( "getScaled", p_getScaled2 ) - .def( "getMerged", p_getMerged1 ) - .def( "getMerged", p_getMerged2 ) - .def( "intersects", p_intersects1 ) - .def( "intersects", p_intersects2 ) - .def( "intersectsClosedInterval", p_intersectsClosed1 ) - .def( "intersectsClosedInterval", p_intersectsClosed2 ) - .def( "intersectionVolume", &AABB::intersectionVolume ) - .def( "getIntersection", &AABB::getIntersection ) - .def( "isIdentical", &AABB::isIdentical ) - .def( "isEqual", &AABB::isEqual ) - .def( "sqDistance", p_sqDistance1 ) - .def( "sqDistance", p_sqDistance2 ) - .def( "sqMaxDistance", p_sqMaxDistance1 ) - .def( "sqMaxDistance", p_sqMaxDistance2 ) - .def( "sqSignedDistance", &AABB::sqSignedDistance) - .def( "distance" , &AABB::distance) - .def( "signedDistance" , &AABB::signedDistance) - .def( "maxDistance" , &AABB::maxDistance) - .def( "extend", p_extend1 ) - .def( "extend", p_extend2 ) - .def( "translate", &AABB::translate ) - .def( "scale", p_scale1 ) - .def( "scale", p_scale2 ) - .def( "merge", p_merge1 ) - .def( "merge", p_merge2 ) - .def( "intersect", &AABB::intersect ) - .def( self_ns::str(self) ) - ; -} - -//====================================================================================================================== -// -// Timing -// -//====================================================================================================================== - -dict buildDictFromTimingNode(const WcTimingNode & tn) -{ - dict result; - - result["all"] = tn.timer_; - for ( auto it = tn.tree_.begin(); it != tn.tree_.end(); ++it) - { - if (it->second.tree_.empty()) - { - result[it->first] = it->second.timer_; - } else - { - result[it->first] = buildDictFromTimingNode(it->second); - } - } - - return result; -} - -dict buildDictFromTimingTree(const WcTimingTree & tt) -{ - return buildDictFromTimingNode( tt.getRawData() ); -} - -void timingTreeStopWrapper(WcTimingTree & tt, const std::string& name) -{ - if (!tt.isTimerRunning(name)) - { - PyErr_SetString( PyExc_ValueError, ("Timer '" + name + "' is currently not running!").c_str() ); - throw error_already_set(); - } - tt.stop(name); -} - -void exportTiming() -{ - class_<WcTimer> ("Timer") - .def( init<>() ) - .def( "start", &WcTimer::start ) - .def( "stop", &WcTimer::end ) - .def( "reset", &WcTimer::reset ) - .def( "merge", &WcTimer::merge ) - .add_property( "counter", &WcTimer::getCounter ) - .add_property( "total", &WcTimer::total ) - .add_property( "sumOfSquares", &WcTimer::sumOfSquares ) - .add_property( "average", &WcTimer::average ) - .add_property( "variance", &WcTimer::variance ) - .add_property( "min", &WcTimer::min ) - .add_property( "max", &WcTimer::max ) - .add_property( "last", &WcTimer::last ) - ; - - - WcTimer & ( WcTimingPool::*pGetItem ) ( const std::string & ) = &WcTimingPool::operator[]; - - { - scope classScope = - class_<WcTimingPool, shared_ptr<WcTimingPool> > ("TimingPool") - .def( init<>() ) - .def( self_ns::str(self) ) - .def( "__getitem__", pGetItem, return_internal_reference<1>() ) - .def( "__contains__", &WcTimingPool::timerExists ) - .def( "getReduced", &WcTimingPool::getReduced, ( arg("targetRank") = 0) ) - .def( "merge", &WcTimingPool::merge, ( arg("mergeDuplicates") = true) ) - .def( "clear", &WcTimingPool::clear ) - .def( "unifyRegisteredTimersAcrossProcesses", &WcTimingPool::unifyRegisteredTimersAcrossProcesses ) - .def( "logResultOnRoot", &WcTimingPool::logResultOnRoot, (arg("unifyRegisteredTimers") = false) ) - .def( self_ns::str(self) ) - ; - - enum_<timing::ReduceType>("ReduceType") - .value("min" , timing::REDUCE_MIN) - .value("avg" , timing::REDUCE_AVG) - .value("max" , timing::REDUCE_MAX) - .value("total", timing::REDUCE_TOTAL) - .export_values() - ; - } - - const WcTimer & ( WcTimingTree::*pTimingTreeGet ) ( const std::string & ) const = &WcTimingTree::operator[]; - class_<WcTimingTree, shared_ptr<WcTimingTree> > ("TimingTree") - .def( init<>() ) - .def( "__getitem__", pTimingTreeGet, return_internal_reference<1>() ) - .def( "start", &WcTimingTree::start ) - .def( "stop", &timingTreeStopWrapper ) - .def( "getReduced", &WcTimingTree::getReduced ) - .def( "toDict", &buildDictFromTimingTree ) - .def( self_ns::str(self) ) - ; - -#if BOOST_VERSION < 106300 - exportSharedPtr<WcTimingTree>(); -#endif - -} - - - - -//====================================================================================================================== -// -// IBlock -// -//====================================================================================================================== - -boost::python::object IBlock_getData( boost::python::object iblockObject, const std::string & stringID ) //NOLINT -{ - IBlock * block = boost::python::extract<IBlock*>( iblockObject ); - - //typedef std::pair< IBlock *, std::string > BlockStringPair; - //static std::map< BlockStringPair, object > cache; - - //auto blockStringPair = std::make_pair( &block, stringID ); - //auto it = cache.find( blockStringPair ); - //if ( it != cache.end() ) - // return it->second; - - BlockDataID id = blockDataIDFromString( *block, stringID ); - - auto manager = python_coupling::Manager::instance(); - boost::python::object res = manager->pythonObjectFromBlockData( *block, id ); - - if ( res == boost::python::object() ) - throw BlockDataNotConvertible(); - - boost::python::objects::make_nurse_and_patient( res.ptr(), iblockObject.ptr() ); - - // write result to cache - //cache[blockStringPair] = res; - //TODO cache has bugs when cache is destroyed, probably since objects are freed after py_finalize is called - //move cache to Manager? - - return res; -} - - -boost::python::list IBlock_blockDataList( boost::python::object iblockObject ) //NOLINT -{ - IBlock * block = boost::python::extract<IBlock*>( iblockObject ); - - const std::vector<std::string> & stringIds = block->getBlockStorage().getBlockDataIdentifiers(); - - boost::python::list resultList; - - for( auto it = stringIds.begin(); it != stringIds.end(); ++it ) { - try { - resultList.append( boost::python::make_tuple( *it, IBlock_getData( iblockObject, *it) ) ); - } - catch( BlockDataNotConvertible & /*e*/ ) { - } - } - - return resultList; -} - -boost::python::object IBlock_iter( boost::python::object iblockObject ) -{ - boost::python::list resultList = IBlock_blockDataList( iblockObject ); //NOLINT - return resultList.attr("__iter__"); -} - -boost::python::tuple IBlock_atDomainMinBorder( IBlock & block ) -{ - return boost::python::make_tuple( block.getBlockStorage().atDomainXMinBorder(block), - block.getBlockStorage().atDomainYMinBorder(block), - block.getBlockStorage().atDomainZMinBorder(block) ); -} - -boost::python::tuple IBlock_atDomainMaxBorder( IBlock & block ) -{ - return boost::python::make_tuple( block.getBlockStorage().atDomainXMaxBorder(block), - block.getBlockStorage().atDomainYMaxBorder(block), - block.getBlockStorage().atDomainZMaxBorder(block) ); -} - -IBlockID::IDType IBlock_getIntegerID( IBlock & block ) -{ - return block.getId().getID(); -} - -bool IBlock_equals( IBlock & block1, IBlock & block2 ) -{ - return block1.getId() == block2.getId(); -} - -std::string IBlock_str( IBlock & b ) { - std::stringstream out; - out << "Block at " << b.getAABB(); - return out.str(); - -} - -void exportIBlock() -{ - register_exception_translator<NoSuchBlockData>( & NoSuchBlockData::translate ); - register_exception_translator<BlockDataNotConvertible>( & BlockDataNotConvertible::translate ); - - class_<IBlock, boost::noncopyable> ("Block", no_init) - .def ( "__getitem__", &IBlock_getData ) - .add_property( "atDomainMinBorder", &IBlock_atDomainMinBorder ) - .add_property( "atDomainMaxBorder", &IBlock_atDomainMaxBorder ) - .add_property( "items", &IBlock_blockDataList) - .add_property( "id", &IBlock_getIntegerID) - .def ( "__hash__", &IBlock_getIntegerID) - .def ( "__eq__", &IBlock_equals) - .def ( "__repr__", &IBlock_str ) - .add_property( "__iter__", &IBlock_iter ) - .add_property("aabb", make_function(&IBlock::getAABB, return_value_policy<copy_const_reference>())) - ; - -} - -//====================================================================================================================== -// -// Logging & Abort -// -//====================================================================================================================== - - -static void wlb_log_devel ( const std::string & msg ) { WALBERLA_LOG_DEVEL ( msg ); } -static void wlb_log_devel_on_root ( const std::string & msg ) { WALBERLA_LOG_DEVEL_ON_ROOT ( msg ); } - -static void wlb_log_result ( const std::string & msg ) { WALBERLA_LOG_RESULT ( msg ); } -static void wlb_log_result_on_root ( const std::string & msg ) { WALBERLA_LOG_RESULT_ON_ROOT ( msg ); } - -static void wlb_log_warning ( const std::string & msg ) { WALBERLA_LOG_WARNING ( msg ); } -static void wlb_log_warning_on_root ( const std::string & msg ) { WALBERLA_LOG_WARNING_ON_ROOT ( msg ); } - -#ifdef WALBERLA_LOGLEVEL_INFO -static void wlb_log_info ( const std::string & msg ) { WALBERLA_LOG_INFO ( msg ); } -static void wlb_log_info_on_root ( const std::string & msg ) { WALBERLA_LOG_INFO_ON_ROOT ( msg ); } -#else -static void wlb_log_info ( const std::string & ) {} -static void wlb_log_info_on_root ( const std::string & ) {} -#endif - -#ifdef WALBERLA_LOGLEVEL_PROGRESS -static void wlb_log_progress ( const std::string & msg ) { WALBERLA_LOG_PROGRESS ( msg ); } -static void wlb_log_progress_on_root ( const std::string & msg ) { WALBERLA_LOG_PROGRESS_ON_ROOT( msg ); } -#else -static void wlb_log_progress ( const std::string & ) {} -static void wlb_log_progress_on_root ( const std::string & ) {} -#endif - -#ifdef WALBERLA_LOGLEVEL_DETAIL -static void wlb_log_detail ( const std::string & msg ) { WALBERLA_LOG_DETAIL ( msg ); } -static void wlb_log_detail_on_root ( const std::string & msg ) { WALBERLA_LOG_DETAIL_ON_ROOT ( msg ); } -#else -static void wlb_log_detail ( const std::string & ) {} -static void wlb_log_detail_on_root ( const std::string & ) {} -#endif - -static void wlb_abort ( const std::string & msg ) { WALBERLA_ABORT_NO_DEBUG_INFO ( msg ); } - -void exportLogging() -{ - def ( "log_devel" , wlb_log_devel ); - def ( "log_devel_on_root" , wlb_log_devel_on_root ); - def ( "log_result", wlb_log_result ); - def ( "log_result_on_root", wlb_log_result_on_root ); - def ( "log_warning", wlb_log_warning ); - def ( "log_warning_on_root", wlb_log_warning_on_root ); - def ( "log_info", wlb_log_info ); - def ( "log_info_on_root", wlb_log_info_on_root ); - def ( "log_progress", wlb_log_progress ); - def ( "log_progress_on_root",wlb_log_progress_on_root); - def ( "log_detail", wlb_log_detail ); - def ( "log_detail_on_root", wlb_log_detail_on_root ); - - def ( "abort", wlb_abort ); -} - - - - -//====================================================================================================================== -// -// StructuredBlockStorage -// -//====================================================================================================================== - - -object * blockDataCreationHelper( IBlock * block, StructuredBlockStorage * bs, object callable ) //NOLINT -{ - object * res = new object( callable( ptr(block), ptr(bs) ) ); - return res; -} - -uint_t StructuredBlockStorage_addBlockData( StructuredBlockStorage & s, const std::string & name, object functionPtr ) //NOLINT -{ - BlockDataID res = s.addStructuredBlockData(name) - << StructuredBlockDataCreator<object>( std::bind( &blockDataCreationHelper, std::placeholders::_1, std::placeholders::_2, functionPtr ) ); - //TODO extend this for moving block data ( packing und unpacking with pickle ) - return res; -} - -// Helper function for iteration over StructuredBlockStorage -// boost::python comes with iteration helpers but non of this worked: -// .def("__iter__" range(&StructuredBlockStorage::begin, &StructuredBlockStorage::end)) -// .def("__iter__", range<return_value_policy<copy_non_const_reference> >( beginPtr, endPtr) ) -boost::python::object StructuredBlockStorage_iter( boost::python::object structuredBlockStorage ) //NOLINT -{ - shared_ptr<StructuredBlockStorage> s = extract< shared_ptr<StructuredBlockStorage> > ( structuredBlockStorage ); - - std::vector< const IBlock* > blocks; - s->getBlocks( blocks ); - boost::python::list resultList; - - for( auto it = blocks.begin(); it != blocks.end(); ++it ) { - boost::python::object theObject( ptr( *it ) ); - // Prevent blockstorage from being destroyed when references to blocks exist - boost::python::objects::make_nurse_and_patient( theObject.ptr(), structuredBlockStorage.ptr() ); - resultList.append( theObject ); - } - - return resultList.attr("__iter__"); -} - - -boost::python::object StructuredBlockStorage_getItem( boost::python::object structuredBlockStorage, uint_t i ) //NOLINT -{ - shared_ptr<StructuredBlockStorage> s = extract< shared_ptr<StructuredBlockStorage> > ( structuredBlockStorage ); - - if ( i >= s->size() ) - { - PyErr_SetString( PyExc_RuntimeError, "Index out of bounds"); - throw error_already_set(); - } - - std::vector< const IBlock* > blocks; - s->getBlocks( blocks ); - - boost::python::object theObject( ptr( blocks[i] ) ); - boost::python::objects::make_nurse_and_patient( theObject.ptr(), structuredBlockStorage.ptr() ); - return theObject; -} - -boost::python::list StructuredBlockStorage_blocksOverlappedByAABB( StructuredBlockStorage & s, const AABB & aabb ) { - std::vector< IBlock*> blocks; - s.getBlocksOverlappedByAABB( blocks, aabb ); - - boost::python::list resultList; - for( auto it = blocks.begin(); it != blocks.end(); ++it ) - resultList.append( ptr( *it ) ); - return resultList; -} - - -boost::python::list StructuredBlockStorage_blocksContainedWithinAABB( StructuredBlockStorage & s, const AABB & aabb ) { - std::vector< IBlock*> blocks; - s.getBlocksContainedWithinAABB( blocks, aabb ); - - boost::python::list resultList; - for( auto it = blocks.begin(); it != blocks.end(); ++it ) - resultList.append( ptr( *it ) ); - return resultList; -} - - -object SbS_transformGlobalToLocal ( StructuredBlockStorage & s, IBlock & block, const object & global ) -{ - if ( extract<CellInterval>( global ).check() ) - { - CellInterval ret; - s.transformGlobalToBlockLocalCellInterval( ret, block, extract<CellInterval>( global ) ); - return object( ret ); - } - else if ( extract<Cell>( global ).check() ) - { - Cell ret; - s.transformGlobalToBlockLocalCell( ret, block, extract<Cell>( global ) ); - return object( ret ); - } - - PyErr_SetString(PyExc_RuntimeError, "Only CellIntervals and cells can be transformed" ); - throw error_already_set(); -} - - -object SbS_transformLocalToGlobal ( StructuredBlockStorage & s, IBlock & block, const object & local ) -{ - if ( extract<CellInterval>( local ).check() ) - { - CellInterval ret; - s.transformBlockLocalToGlobalCellInterval( ret, block, extract<CellInterval>( local ) ); - return object( ret ); - } - else if ( extract<Cell>( local ).check() ) - { - Cell ret; - s.transformBlockLocalToGlobalCell( ret, block, extract<Cell>( local ) ); - return object( ret ); - } - PyErr_SetString(PyExc_RuntimeError, "Only CellIntervals and cells can be transformed" ); - throw error_already_set(); -} - -void SbS_writeBlockData( StructuredBlockStorage & s,const std::string & blockDataId, const std::string & file ) -{ - mpi::SendBuffer buffer; - s.serializeBlockData( blockDataIDFromString(s, blockDataId), buffer); - mpi::writeMPIIO(file, buffer); -} - -void SbS_readBlockData( StructuredBlockStorage & s,const std::string & blockDataId, const std::string & file ) -{ - mpi::RecvBuffer buffer; - mpi::readMPIIO(file, buffer); - - s.deserializeBlockData( blockDataIDFromString(s, blockDataId), buffer ); - if ( ! buffer.isEmpty() ) { - PyErr_SetString(PyExc_RuntimeError, "Reading failed - file does not contain matching data for this type." ); - throw error_already_set(); - } -} - -CellInterval SbS_getBlockCellBB( StructuredBlockStorage & s, const IBlock * block ) -{ - return s.getBlockCellBB( *block ); -} - - -Vector3<real_t> SbS_mapToPeriodicDomain1 ( StructuredBlockStorage & s, real_t x, real_t y, real_t z ) -{ - Vector3<real_t> res ( x,y,z ); - s.mapToPeriodicDomain( res ); - return res; -} -Vector3<real_t> SbS_mapToPeriodicDomain2 ( StructuredBlockStorage & s, Vector3<real_t> in ) -{ - s.mapToPeriodicDomain( in ); - return in; -} -Cell SbS_mapToPeriodicDomain3 ( StructuredBlockStorage & s, Cell in, uint_t level = 0 ) -{ - s.mapToPeriodicDomain( in, level ); - return in; -} - -object SbS_getBlock1 ( StructuredBlockStorage & s, const real_t x , const real_t y , const real_t z ) { - return object( ptr( s.getBlock( x,y,z ) ) ); - -} - -object SbS_getBlock2 ( StructuredBlockStorage & s, const Vector3<real_t> & v ) { - return object( ptr( s.getBlock( v ) ) ); -} - - -tuple SbS_periodic( StructuredBlockStorage & s ) -{ - return make_tuple( s.isXPeriodic(), s.isYPeriodic(), s.isZPeriodic() ); -} - -bool SbS_atDomainXMinBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainXMinBorder( *b ); } -bool SbS_atDomainXMaxBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainXMaxBorder( *b ); } -bool SbS_atDomainYMinBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainYMinBorder( *b ); } -bool SbS_atDomainYMaxBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainYMaxBorder( *b ); } -bool SbS_atDomainZMinBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainZMinBorder( *b ); } -bool SbS_atDomainZMaxBorder( StructuredBlockStorage & s, const IBlock * b ) { return s.atDomainZMaxBorder( *b ); } - -void exportStructuredBlockStorage() -{ - bool ( StructuredBlockStorage::*p_blockExists1 ) ( const Vector3< real_t > & ) const = &StructuredBlockStorage::blockExists; - bool ( StructuredBlockStorage::*p_blockExistsLocally1 ) ( const Vector3< real_t > & ) const = &StructuredBlockStorage::blockExistsLocally; - bool ( StructuredBlockStorage::*p_blockExistsRemotely1 ) ( const Vector3< real_t > & ) const = &StructuredBlockStorage::blockExistsRemotely; - - bool ( StructuredBlockStorage::*p_blockExists2 ) ( const real_t, const real_t, const real_t ) const = &StructuredBlockStorage::blockExists; - bool ( StructuredBlockStorage::*p_blockExistsLocally2 ) ( const real_t, const real_t, const real_t ) const = &StructuredBlockStorage::blockExistsLocally; - bool ( StructuredBlockStorage::*p_blockExistsRemotely2 ) ( const real_t, const real_t, const real_t ) const = &StructuredBlockStorage::blockExistsRemotely; - - class_<StructuredBlockStorage, shared_ptr<StructuredBlockStorage>, boost::noncopyable>("StructuredBlockStorage", no_init ) - .def( "getNumberOfLevels", &StructuredBlockStorage::getNumberOfLevels ) - .def( "getDomain", &StructuredBlockStorage::getDomain, return_internal_reference<1>() ) - .def( "mapToPeriodicDomain", &SbS_mapToPeriodicDomain1 ) - .def( "mapToPeriodicDomain", &SbS_mapToPeriodicDomain2 ) - .def( "mapToPeriodicDomain", &SbS_mapToPeriodicDomain3, (arg("level") = 0 ) ) - .def( "addBlockData", &StructuredBlockStorage_addBlockData ) - .def( "__getitem__", &StructuredBlockStorage_getItem ) - .def( "__len__", &StructuredBlockStorage::size ) - .def( "getBlock", SbS_getBlock1 ) - .def( "getBlock", SbS_getBlock2 ) - .def( "containsGlobalBlockInformation", &StructuredBlockStorage::containsGlobalBlockInformation ) - .def( "blocksOverlappedByAABB" , &StructuredBlockStorage_blocksOverlappedByAABB ) - .def( "blocksContainedWithinAABB", &StructuredBlockStorage_blocksContainedWithinAABB ) - .def( "blockExists", p_blockExists1 ) - .def( "blockExists", p_blockExists2 ) - .def( "blockExistsLocally", p_blockExistsLocally1 ) - .def( "blockExistsLocally", p_blockExistsLocally2 ) - .def( "blockExistsRemotely", p_blockExistsRemotely1 ) - .def( "blockExistsRemotely", p_blockExistsRemotely2 ) - .def( "atDomainXMinBorder", &SbS_atDomainXMinBorder ) - .def( "atDomainXMaxBorder", &SbS_atDomainXMaxBorder ) - .def( "atDomainYMinBorder", &SbS_atDomainYMinBorder ) - .def( "atDomainYMaxBorder", &SbS_atDomainYMaxBorder ) - .def( "atDomainZMinBorder", &SbS_atDomainZMinBorder ) - .def( "atDomainZMaxBorder", &SbS_atDomainZMaxBorder ) - .def( "dx", &StructuredBlockStorage::dx, ( args("level")=0 ) ) - .def( "dy", &StructuredBlockStorage::dy, ( args("level")=0 ) ) - .def( "dz", &StructuredBlockStorage::dz, ( args("level")=0 ) ) - .def( "getDomainCellBB", &StructuredBlockStorage::getDomainCellBB, return_value_policy<copy_const_reference>(), ( args( "level") = 0 ) ) - .def( "getBlockCellBB", &SbS_getBlockCellBB ) - .def( "transformGlobalToLocal", &SbS_transformGlobalToLocal ) - .def( "transformLocalToGlobal", &SbS_transformLocalToGlobal ) - .def( "writeBlockData", &SbS_writeBlockData ) - .def( "readBlockData", &SbS_readBlockData ) - .add_property("__iter__", &StructuredBlockStorage_iter ) - .add_property( "containsGlobalBlockInformation", &StructuredBlockStorage::containsGlobalBlockInformation ) - .add_property( "periodic", &SbS_periodic ) - ; - -#if BOOST_VERSION < 106300 - exportSharedPtr<StructuredBlockStorage>(); -#endif -} - -//====================================================================================================================== -// -// Communication -// -//====================================================================================================================== - -void exportCommunication() -{ - using communication::UniformPackInfo; - class_< UniformPackInfo, shared_ptr<UniformPackInfo>, boost::noncopyable> //NOLINT - ( "UniformPackInfo", no_init ); - - using communication::UniformMPIDatatypeInfo; - class_< UniformMPIDatatypeInfo, shared_ptr<UniformMPIDatatypeInfo>, boost::noncopyable> - ( "UniformMPIDatatypeInfo", no_init ); - -#if BOOST_VERSION < 106300 - exportSharedPtr<UniformPackInfo>(); - exportSharedPtr<UniformMPIDatatypeInfo>(); -#endif -} - -//====================================================================================================================== -// -// Stencil Directions -// -//====================================================================================================================== - -void exportStencilDirections() -{ - ModuleScope build_info( "stencil"); - - enum_<stencil::Direction>("Direction") - .value("C" , stencil::C ) - .value("N" , stencil::N ) - .value("S" , stencil::S ) - .value("W" , stencil::W ) - .value("E" , stencil::E ) - .value("T" , stencil::T ) - .value("B" , stencil::B ) - .value("NW" , stencil::NW ) - .value("NE" , stencil::NE ) - .value("SW" , stencil::SW ) - .value("SE" , stencil::SE ) - .value("TN" , stencil::TN ) - .value("TS" , stencil::TS ) - .value("TW" , stencil::TW ) - .value("TE" , stencil::TE ) - .value("BN" , stencil::BN ) - .value("BS" , stencil::BS ) - .value("BW" , stencil::BW ) - .value("BE" , stencil::BE ) - .value("TNE", stencil::TNE) - .value("TNW", stencil::TNW) - .value("TSE", stencil::TSE) - .value("TSW", stencil::TSW) - .value("BNE", stencil::BNE) - .value("BNW", stencil::BNW) - .value("BSE", stencil::BSE) - .value("BSW", stencil::BSW) - .export_values() - ; - boost::python::list cx; - - boost::python::list cy; - - boost::python::list cz; - - boost::python::list dirStrings; - for( uint_t i=0; i < stencil::NR_OF_DIRECTIONS; ++i ) - { - cx.append( stencil::cx[i] ); - cy.append( stencil::cy[i] ); - cz.append( stencil::cz[i] ); - dirStrings.append( stencil::dirToString[i] ); - } - boost::python::list c; - c.append( cx ); - c.append( cy ); - c.append( cz ); - - using boost::python::scope; - scope().attr("cx") = cx; - scope().attr("cy") = cy; - scope().attr("cz") = cz; - scope().attr("c") = c; - scope().attr("dirStrings") = dirStrings; -} - - -//====================================================================================================================== -// -// Build Info -// -//====================================================================================================================== - - -void exportBuildInfo() -{ - ModuleScope build_info( "build_info"); - using boost::python::scope; - scope().attr("version") = WALBERLA_GIT_SHA1; - scope().attr("type" ) = WALBERLA_BUILD_TYPE; - scope().attr("compiler_flags" ) = WALBERLA_COMPILER_FLAGS; - scope().attr("build_machine" ) = WALBERLA_BUILD_MACHINE; - scope().attr("source_dir") = WALBERLA_SOURCE_DIR; - scope().attr("build_dir") = WALBERLA_BUILD_DIR; -} - - - - -void exportBasicWalberlaDatastructures() -{ - - NumpyIntConversion<uint8_t>(); - NumpyIntConversion<int32_t>(); - NumpyIntConversion<int64_t>(); - NumpyIntConversion<uint32_t>(); - NumpyIntConversion<uint64_t>(); - NumpyIntConversion<size_t>(); - NumpyIntConversion<bool>(); - NumpyFloatConversion<float>(); - NumpyFloatConversion<double>(); - NumpyFloatConversion<long double>(); - - - exportMPI(); - - exportBuildInfo(); - exportVector3(); - exportCell(); - exportCellInterval(); - exportAABB(); - - exportTiming(); - - exportIBlock(); - exportStructuredBlockStorage(); - exportCommunication(); - - exportLogging(); - exportStencilDirections(); - - // Add empty callbacks module - object callbackModule( handle<>( borrowed(PyImport_AddModule("walberla_cpp.callbacks")))); - scope().attr("callbacks") = callbackModule; - -} - -} // namespace python_coupling -} // namespace walberla - -#endif - diff --git a/src/python_coupling/basic_exports/MPIExport.cpp b/src/python_coupling/basic_exports/MPIExport.cpp deleted file mode 100644 index 5811cbab5..000000000 --- a/src/python_coupling/basic_exports/MPIExport.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "python_coupling/helper/PythonIterableToStdVector.h" - -#include "core/mpi/MPIManager.h" -#include "core/mpi/Reduce.h" -#include "core/mpi/Gather.h" -#include "core/mpi/Broadcast.h" - -#include <vector> - -using namespace boost::python; - - - - -namespace walberla { -namespace python_coupling { - - typedef std::vector<int64_t> IntStdVector; - typedef std::vector<real_t> RealStdVector; - typedef std::vector<std::string> StringStdVector; - - - //=================================================================================================================== - // - // MPIManager - // - //=================================================================================================================== - - - static int rank() { return MPIManager::instance()->rank(); } - static int worldRank() { return MPIManager::instance()->worldRank(); } - static int numProcesses() { return MPIManager::instance()->numProcesses(); } - static bool hasCartesianSetup() { return MPIManager::instance()->hasCartesianSetup(); } - static bool rankValid() { return MPIManager::instance()->rankValid(); } - - - - //=================================================================================================================== - // - // Broadcast - // - //=================================================================================================================== - - static object broadcast_string( object value, int sendRank ) //NOLINT - { - if ( extract<std::string>(value).check() ) - { - std::string extractedValue = extract< std::string >(value); - mpi::broadcastObject( extractedValue , sendRank ); - return object( extractedValue ); - } - StringStdVector extractedValue = pythonIterableToStdVector< StringStdVector::value_type >( value ); - mpi::broadcastObject( extractedValue, sendRank ); - return object( extractedValue ); - } - - static object broadcast_int( object value, int sendRank ) //NOLINT - { - if ( extract<int64_t>(value).check() ) - { - int64_t extractedValue = extract< int64_t >(value); - mpi::broadcastObject( extractedValue , sendRank ); - return object( extractedValue ); - } - IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); - mpi::broadcastObject( extractedValue, sendRank ); - return object( extractedValue ); - } - - static object broadcast_real( object value, int sendRank ) //NOLINT - { - if ( extract<real_t>(value).check() ) - { - real_t extractedValue = extract< real_t >(value); - mpi::broadcastObject( extractedValue , sendRank); - return object( extractedValue ); - } - RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); - mpi::broadcastObject( extractedValue , sendRank); - return object( extractedValue ); - } - - - //=================================================================================================================== - // - // Reduce - // - //=================================================================================================================== - - - static object reduce_int( object value, mpi::Operation op, int recvRank ) //NOLINT - { - if ( extract<int64_t>(value).check() ) - { - int64_t extractedValue = extract< int64_t >(value); - mpi::reduceInplace( extractedValue , op, recvRank ); - return object( extractedValue ); - } - IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); - mpi::reduceInplace( extractedValue, op, recvRank ); - return object( extractedValue ); - } - - static object reduce_real( object value, mpi::Operation op, int recvRank ) //NOLINT - { - if ( extract<real_t>(value).check() ) - { - real_t extractedValue = extract< real_t >(value); - mpi::reduceInplace( extractedValue , op, recvRank); - return object( extractedValue ); - } - RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); - mpi::reduceInplace( extractedValue , op, recvRank); - return object( extractedValue ); - } - - - static object allreduce_int( object value, mpi::Operation op ) //NOLINT - { - if ( extract<int64_t>(value).check() ) - { - int64_t extractedValue = extract< int64_t >(value); - mpi::allReduceInplace( extractedValue , op ); - return object( extractedValue ); - } - IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); - mpi::allReduceInplace( extractedValue, op ); - return object( extractedValue ); - } - - static object allreduce_real( object value, mpi::Operation op ) //NOLINT - { - if ( extract<real_t>(value).check() ) - { - real_t extractedValue = extract< real_t >(value); - mpi::allReduceInplace( extractedValue , op ); - return object( extractedValue ); - } - RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); - mpi::allReduceInplace( extractedValue , op ); - return object( extractedValue ); - } - - - //=================================================================================================================== - // - // Gather - // - //=================================================================================================================== - - static IntStdVector gather_int( object value, int recvRank ) //NOLINT - { - if ( ! extract<int64_t>(value).check() ) - { - PyErr_SetString( PyExc_RuntimeError, "Could not gather the given value - unknown type"); - throw error_already_set(); - } - int64_t extractedValue = extract< int64_t >(value); - return mpi::gather( extractedValue , recvRank ); - } - - static RealStdVector gather_real( object value, int recvRank ) //NOLINT - { - if ( ! extract<real_t>(value).check() ) - { - PyErr_SetString( PyExc_RuntimeError, "Could not gather the given value - unknown type"); - throw error_already_set(); - } - real_t extractedValue = extract< real_t >(value); - return mpi::gather( extractedValue , recvRank); - } - - - static IntStdVector allgather_int( object value ) //NOLINT - { - if ( ! extract<int64_t>(value).check() ) - { - PyErr_SetString( PyExc_RuntimeError, "Could not gather the given value - unknown type"); - throw error_already_set(); - } - int64_t extractedValue = extract< int64_t >(value); - return mpi::allGather( extractedValue ); - } - - static RealStdVector allgather_real( object value ) //NOLINT - { - if ( ! extract<real_t>(value).check() ) - { - PyErr_SetString( PyExc_RuntimeError, "Could not gather the given value - unknown type"); - throw error_already_set(); - } - real_t extractedValue = extract< real_t >(value); - return mpi::allGather( extractedValue ); - } - - - - //=================================================================================================================== - // - // Export - // - //=================================================================================================================== - - static void worldBarrier() - { - WALBERLA_MPI_WORLD_BARRIER(); - } - - - void exportMPI() - { - object mpiModule( handle<>( borrowed(PyImport_AddModule("walberla_cpp.mpi")))); - scope().attr("mpi") = mpiModule; - scope mpiScope = mpiModule; - - def( "rank" , &rank ); - def( "worldRank" , &worldRank ); - def( "numProcesses" , &numProcesses ); - def( "hasCartesianSetup", &hasCartesianSetup); - def( "rankValid" , &rankValid ); - def( "worldBarrier" , &worldBarrier ); - - enum_<mpi::Operation>("Operation") - .value("MIN" , mpi::MIN ) - .value("MAX" , mpi::MAX ) - .value("SUM" , mpi::SUM ) - .value("PRODUCT", mpi::PRODUCT ) - .value("LOGICAL_AND", mpi::LOGICAL_AND ) - .value("BITWISE_AND", mpi::BITWISE_AND ) - .value("LOGICAL_OR", mpi::LOGICAL_OR ) - .value("BITWISE_OR", mpi::BITWISE_OR ) - .value("LOGICAL_XOR", mpi::LOGICAL_XOR ) - .value("BITWISE_XOR", mpi::BITWISE_XOR ) - .export_values(); - - def( "broadcastInt", &broadcast_int, ( arg("sendRank") = 0) ); - def( "broadcastReal", &broadcast_real, ( arg("sendRank") = 0) ); - def( "broadcastString",&broadcast_string, ( arg("sendRank") = 0) ); - - def( "reduceInt", &reduce_int, ( arg("recvRank") = 0 ) ); - def( "reduceReal", &reduce_real, ( arg("recvRank") = 0 ) ); - def( "allreduceInt", &allreduce_int ); - def( "allreduceReal", &allreduce_real ); - - - class_< IntStdVector> ("IntStdVector") .def(vector_indexing_suite<IntStdVector>() ); - class_< RealStdVector> ("RealStdVector").def(vector_indexing_suite<RealStdVector>() ); - class_< StringStdVector>("StringStdVector").def(vector_indexing_suite<StringStdVector>() ); - - def( "gatherInt", &gather_int , ( arg("recvRank") = 0 ) ); - def( "gatherReal", &gather_real, ( arg("recvRank") = 0 ) ); - def( "allgatherInt", &allgather_int ); - def( "allgatherReal", &allgather_real ); - } - - - -} // namespace python_coupling -} // namespace walberla - - -#endif diff --git a/src/python_coupling/export/BasicExport.cpp b/src/python_coupling/export/BasicExport.cpp new file mode 100644 index 000000000..279e304e5 --- /dev/null +++ b/src/python_coupling/export/BasicExport.cpp @@ -0,0 +1,728 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file BasicExports.cpp +//! \ingroup python_coupling +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +// Do not reorder includes - the include order is important +#include "python_coupling/PythonWrapper.h" + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +# include "communication/UniformMPIDatatypeInfo.h" +# include "communication/UniformPackInfo.h" + +# include "core/Abort.h" +# include "core/cell/CellInterval.h" +# include "core/logging/Logging.h" +# include "core/math/AABB.h" +# include "core/mpi/MPIIO.h" +# include "core/timing/ReduceType.h" +# include "core/timing/TimingPool.h" +# include "core/timing/TimingTree.h" +# include "core/waLBerlaBuildInfo.h" + +# include "domain_decomposition/StructuredBlockStorage.h" + +# include "field/GhostLayerField.h" + +# include "python_coupling/Manager.h" +# include "python_coupling/helper/BlockStorageExportHelpers.h" +# include "python_coupling/helper/OwningIterator.h" + +# include "stencil/Directions.h" + +# include <functional> + +# include "BasicExport.h" +# include "MPIExport.h" + +# include <pybind11/stl.h> + +// specialize operator== since == is deprecated in pybind11 +template<> +bool walberla::domain_decomposition::internal::BlockData::Data< pybind11::object >::operator==( + const BlockData::DataBase& rhs) const +{ + const Data< pybind11::object >* rhsData = dynamic_cast< const Data< pybind11::object >* >(&rhs); + return (rhsData == &rhs) && (data_->is(*(rhsData->data_))); +} + +namespace py = pybind11; +namespace walberla { +namespace python_coupling { + +//====================================================================================================================== +// +// Cell +// +//====================================================================================================================== + +void exportCell(py::module_ &m) +{ + py::class_<Cell>(m, "Cell") + .def( py::init<cell_idx_t, cell_idx_t, cell_idx_t>()) + .def("__getitem__", + [](const Cell & cell, py::object & idx){ + return py::make_tuple(cell.x(), cell.y(), cell.z()).attr("__getitem__")(idx); + }); +} + +//====================================================================================================================== +// +// CellInterval +// +//====================================================================================================================== + + +void cellInterval_setMin( CellInterval & ci, const Cell & min ) { + ci.min() = min; +} +void cellInterval_setMax( CellInterval & ci, const Cell & max ) { + ci.max() = max; +} +void cellInterval_shift( CellInterval & ci, cell_idx_t xShift, cell_idx_t yShift, cell_idx_t zShift ) { + ci.shift( xShift, yShift, zShift ); +} + +py::tuple cellInterval_size( CellInterval & ci ) { + return py::make_tuple( ci.xSize(), ci.ySize(), ci.zSize() ); +} + +py::tuple cellInterval_min( CellInterval & ci ) { + return py::make_tuple( ci.xMin(), ci.yMin(), ci.zMin() ); +} + +py::tuple cellInterval_max( CellInterval & ci ) { + return py::make_tuple( ci.xMax(), ci.yMax(), ci.zMax() ); +} + +CellInterval cellInterval_getIntersection( CellInterval & ci1, CellInterval & ci2 ) +{ + CellInterval result ( ci1 ); + result.intersect( ci2 ); + return result; +} + +CellInterval cellInterval_getShifted( CellInterval & ci1, cell_idx_t xShift, cell_idx_t yShift, cell_idx_t zShift ) +{ + CellInterval result ( ci1 ); + result.shift( xShift, yShift, zShift ); + return result; +} + +CellInterval cellInterval_getExpanded1( CellInterval & ci1, cell_idx_t expandVal ) +{ + CellInterval result ( ci1 ); + result.expand( expandVal ); + return result; +} + +CellInterval cellInterval_getExpanded2( CellInterval & ci1, cell_idx_t xExpand, cell_idx_t yExpand, cell_idx_t zExpand ) +{ + CellInterval result ( ci1 ); + result.expand( Cell(xExpand, yExpand, zExpand) ); + return result; +} + +void exportCellInterval(py::module_ &m) +{ + using namespace pybind11::literals; + bool ( CellInterval::*p_contains1) ( const Cell & ) const = &CellInterval::contains; + bool ( CellInterval::*p_contains2) ( const CellInterval & ) const = &CellInterval::contains; + + void ( CellInterval::*p_expand1) ( const cell_idx_t ) = &CellInterval::expand; + void ( CellInterval::*p_expand2) ( const Cell & ) = &CellInterval::expand; + + bool ( CellInterval::*p_overlaps ) ( const CellInterval & ) const = &CellInterval::overlaps; + + py::class_<CellInterval>(m, "CellInterval") + .def( py::init<cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t, cell_idx_t>()) + .def_property( "min", &cellInterval_min, &cellInterval_setMin ) + .def_property( "max", &cellInterval_max, &cellInterval_setMax ) + .def_property_readonly( "size", &cellInterval_size ) + .def( "empty", &CellInterval::empty ) + .def( "positiveIndicesOnly", &CellInterval::positiveIndicesOnly ) + .def( "contains", p_contains1 ) + .def( "contains", p_contains2 ) + .def( "overlaps", p_overlaps ) + .def( "shift", &cellInterval_shift ) + .def( "getShifted", &cellInterval_getShifted ) + .def( "expand", p_expand1 ) + .def( "expand", p_expand2 ) + .def( "getExpanded", &cellInterval_getExpanded1 ) + .def( "getExpanded", &cellInterval_getExpanded2 ) + .def( "intersect", &CellInterval::intersect ) + .def( "getIntersection", &cellInterval_getIntersection ) + .def("__eq__", &CellInterval::operator==) + .def("__ne__", &CellInterval::operator!=) + .def_property_readonly( "numCells", &CellInterval::numCells ) + ; +} + +//====================================================================================================================== +// +// AABB +// +//====================================================================================================================== + + +py::tuple aabb_getMin( const AABB & domainBB ) { + return py::make_tuple( domainBB.xMin(), domainBB.yMin(), domainBB.zMin() ); +} + +py::tuple aabb_getMax( const AABB & domainBB ) { + return py::make_tuple( domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); +} + +py::tuple aabb_getSize( const AABB & domainBB ) { + return py::make_tuple( domainBB.sizes()[0], domainBB.sizes()[1], domainBB.sizes()[2] ); +} + +py::tuple aabb_getCenter( const AABB & domainBB ) { + return py::make_tuple( domainBB.center()[0], domainBB.center()[1], domainBB.center()[2] ); +} + +bool p_containsVec( const AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.contains(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +bool p_containsClosedInterval1( const AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.containsClosedInterval(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +bool p_containsClosedInterval2( const AABB & domainBB, std::array< real_t , 3 > Point, real_t dx ) { + return domainBB.containsClosedInterval(Vector3<real_t>(Point[0], Point[1], Point[2]), dx); +} + +AABB p_getExtended2( const AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.getExtended(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +AABB p_getScaled2( const AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.getScaled(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +AABB p_getMerged2( const AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.getMerged(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +void p_extend2( AABB & domainBB, std::array< real_t , 3 > Point ) { + domainBB.extend(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +void p_scale2( AABB & domainBB, std::array< real_t , 3 > Point ) { + domainBB.scale(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +void p_merge2( AABB & domainBB, std::array< real_t , 3 > Point ) { + domainBB.merge(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +real_t p_distance( AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.distance(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +real_t p_signedDistance( AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.signedDistance(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +real_t p_maxDistance( AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.distance(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +real_t p_sqDistance2( AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.sqDistance(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +real_t p_sqMaxDistance2( AABB & domainBB, std::array< real_t , 3 > Point ) { + return domainBB.sqMaxDistance(Vector3<real_t>(Point[0], Point[1], Point[2])); +} + +void aabb_setMin( AABB & domainBB, const std::array< real_t , 3 >& min ) +{ + domainBB = AABB( domainBB.max(), AABB::vector_type ( min[0], min[1], min[2] ) ); +} + +void aabb_setMax( AABB & domainBB, const std::array< real_t , 3 >& max ) +{ + domainBB = AABB( domainBB.min(), AABB::vector_type ( max[0], max[1], max[2] ) ); +} + + +void exportAABB(py::module_ &m) +{ + bool ( AABB::*p_containsBB )( const AABB & bb ) const = &AABB::contains; + + AABB ( AABB::*p_getExtended1 ) ( const real_t ) const = &AABB::getExtended; + + AABB ( AABB::*p_getScaled1 ) ( const real_t ) const = &AABB::getScaled; + + AABB ( AABB::*p_getMerged1 ) ( const AABB & ) const = &AABB::getMerged; + + bool ( AABB::*p_intersects1 )( const AABB & bb ) const = &AABB::intersects; + bool ( AABB::*p_intersects2 )( const AABB & bb, real_t dx ) const = &AABB::intersects; + + bool ( AABB::*p_intersectsClosed1 )( const AABB & bb ) const = &AABB::intersectsClosedInterval; + bool ( AABB::*p_intersectsClosed2 )( const AABB & bb, real_t dx ) const = &AABB::intersectsClosedInterval; + + void ( AABB::*p_extend1 ) ( const real_t ) = &AABB::extend; + + void ( AABB::*p_scale1 ) ( const real_t ) = &AABB::scale; + + void ( AABB::*p_merge1 ) ( const AABB & ) = &AABB::merge; + + real_t ( AABB::*p_sqDistance1 ) ( const AABB & ) const = &AABB::sqDistance; + + real_t ( AABB::*p_sqMaxDistance1 ) ( const AABB & ) const = &AABB::sqMaxDistance; + + + py::class_<AABB>(m, "AABB") + .def( py::init<real_t,real_t,real_t,real_t,real_t,real_t>() ) + .def("__eq__", &walberla::math::operator==<real_t, real_t > ) + .def("__ne__", &walberla::math::operator!=<real_t, real_t > ) + .def_property( "min", &aabb_getMin, &aabb_setMin ) + .def_property( "max", &aabb_getMax, &aabb_setMax ) + .def_property_readonly( "size", &aabb_getSize ) + .def_property_readonly( "empty", &AABB::empty ) + .def_property_readonly( "volume", &AABB::volume ) + .def_property_readonly( "center", &aabb_getCenter ) + .def( "contains", p_containsBB ) + .def( "contains", &p_containsVec ) + .def( "containsClosedInterval", &p_containsClosedInterval1 ) + .def( "containsClosedInterval", &p_containsClosedInterval2 ) + .def( "getExtended", p_getExtended1 ) + .def( "getExtended", &p_getExtended2 ) + .def( "getTranslated", &AABB::getTranslated ) + .def( "getScaled", p_getScaled1 ) + .def( "getScaled", &p_getScaled2 ) + .def( "getMerged", p_getMerged1 ) + .def( "getMerged", &p_getMerged2 ) + .def( "intersects", p_intersects1 ) + .def( "intersects", p_intersects2 ) + .def( "intersectsClosedInterval", p_intersectsClosed1 ) + .def( "intersectsClosedInterval", p_intersectsClosed2 ) + .def( "intersectionVolume", &AABB::intersectionVolume ) + .def( "getIntersection", &AABB::getIntersection ) + .def( "isIdentical", &AABB::isIdentical ) + .def( "isEqual", &AABB::isEqual ) + .def( "sqDistance", p_sqDistance1 ) + .def( "sqDistance", &p_sqDistance2 ) + .def( "sqMaxDistance", p_sqMaxDistance1 ) + .def( "sqMaxDistance", &p_sqMaxDistance2 ) + .def( "sqSignedDistance", &AABB::sqSignedDistance) + .def( "distance" , &p_distance) + .def( "signedDistance" , &p_signedDistance) + .def( "maxDistance" , &p_maxDistance) + .def( "extend", p_extend1 ) + .def( "extend", &p_extend2 ) + .def( "translate", &AABB::translate ) + .def( "scale", p_scale1 ) + .def( "scale", &p_scale2 ) + .def( "merge", p_merge1 ) + .def( "merge", &p_merge2 ) + .def( "intersect", &AABB::intersect ) + ; +} + +//====================================================================================================================== +// +// Timing +// +//====================================================================================================================== + +py::dict buildDictFromTimingNode(const WcTimingNode & tn) +{ + py::dict result; + + result["all"] = tn.timer_; + for ( auto it = tn.tree_.begin(); it != tn.tree_.end(); ++it) + { + std::string key = it->first; + if (it->second.tree_.empty()) + { + result[key.c_str()] = it->second.timer_; + } else + { + result[key.c_str()] = buildDictFromTimingNode(it->second); + } + } + + return result; +} + +py::dict buildDictFromTimingTree(const WcTimingTree & tt) +{ + return buildDictFromTimingNode( tt.getRawData() ); +} + +void timingTreeStopWrapper(WcTimingTree & tt, const std::string& name) +{ + if (!tt.isTimerRunning(name)) + { + throw py::value_error(("Timer '" + name + "' is currently not running!").c_str()); + } + tt.stop(name); +} + +void exportTiming(py::module_ &m) +{ + py::class_<WcTimer> (m, "Timer") + .def( py::init<>() ) + .def( "start", &WcTimer::start ) + .def( "stop", &WcTimer::end ) + .def( "reset", &WcTimer::reset ) + .def( "merge", &WcTimer::merge ) + .def_property_readonly( "counter", &WcTimer::getCounter ) + .def_property_readonly( "total", &WcTimer::total ) + .def_property_readonly( "sumOfSquares", &WcTimer::sumOfSquares ) + .def_property_readonly( "average", &WcTimer::average ) + .def_property_readonly( "variance", &WcTimer::variance ) + .def_property_readonly( "min", &WcTimer::min ) + .def_property_readonly( "max", &WcTimer::max ) + .def_property_readonly( "last", &WcTimer::last ) + ; + + + WcTimer & ( WcTimingPool::*pGetItem ) ( const std::string & ) = &WcTimingPool::operator[]; + + { + py::scope classScope = + py::class_<WcTimingPool, shared_ptr<WcTimingPool> > (m, "TimingPool") + .def( py::init<>() ) + .def_property_readonly( "__getitem__", pGetItem) + .def( "__contains__", &WcTimingPool::timerExists ) + .def( "getReduced", &WcTimingPool::getReduced) + .def( "merge", &WcTimingPool::merge) + .def( "clear", &WcTimingPool::clear ) + .def( "unifyRegisteredTimersAcrossProcesses", &WcTimingPool::unifyRegisteredTimersAcrossProcesses ) + .def( "logResultOnRoot", &WcTimingPool::logResultOnRoot) + ; + WALBERLA_UNUSED( classScope ); + + py::enum_<timing::ReduceType>(m, "ReduceType") + .value("min" , timing::REDUCE_MIN) + .value("avg" , timing::REDUCE_AVG) + .value("max" , timing::REDUCE_MAX) + .value("total", timing::REDUCE_TOTAL) + .export_values() + ; + } + + const WcTimer & ( WcTimingTree::*pTimingTreeGet ) ( const std::string & ) const = &WcTimingTree::operator[]; + py::class_<WcTimingTree, shared_ptr<WcTimingTree> > (m, "TimingTree") + .def( py::init<>() ) + .def_property_readonly( "__getitem__", pTimingTreeGet ) + .def( "start", &WcTimingTree::start ) + .def( "stop", &timingTreeStopWrapper ) + .def( "getReduced", &WcTimingTree::getReduced ) + .def( "toDict", &buildDictFromTimingTree ) + ; +} + + + + +//====================================================================================================================== +// +// IBlock +// +//====================================================================================================================== + +py::object IBlock_getData( py::object iblockObject, const std::string & stringID ) //NOLINT +{ + IBlock * block = py::cast<IBlock*>( iblockObject ); + + BlockDataID id = blockDataIDFromString( *block, stringID ); + + auto manager = python_coupling::Manager::instance(); + py::object res = manager->pythonObjectFromBlockData( *block, id ); + + if ( res.is(py::object()) ) + throw BlockDataNotConvertible(); + + return manager->pythonObjectFromBlockData( *block, id ); +} + + +std::vector<std::string> IBlock_fieldNames( py::object iblockObject ) //NOLINT +{ + IBlock * block = py::cast<IBlock*>( iblockObject ); + + return block->getBlockStorage().getBlockDataIdentifiers(); +} + +py::tuple IBlock_atDomainMinBorder( IBlock & block ) +{ + return py::make_tuple( block.getBlockStorage().atDomainXMinBorder(block), + block.getBlockStorage().atDomainYMinBorder(block), + block.getBlockStorage().atDomainZMinBorder(block) ); +} + +py::tuple IBlock_atDomainMaxBorder( IBlock & block ) +{ + return py::make_tuple( block.getBlockStorage().atDomainXMaxBorder(block), + block.getBlockStorage().atDomainYMaxBorder(block), + block.getBlockStorage().atDomainZMaxBorder(block) ); +} + +IBlockID::IDType IBlock_getIntegerID( IBlock & block ) +{ + return block.getId().getID(); +} + +bool IBlock_equals( IBlock & block1, IBlock & block2 ) +{ + return block1.getId() == block2.getId(); +} + +std::string IBlock_str( IBlock & b ) { + std::stringstream out; + out << "Block at " << b.getAABB(); + return out.str(); + +} + +void exportIBlock(py::module_ &m) +{ + static py::exception<NoSuchBlockData> ex(m, "NoSuchBlockData"); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } catch (const NoSuchBlockData &e) { + // Set NoSuchBlockData as the active python error + throw std::out_of_range(e.what()); + } + }); + static py::exception<BlockDataNotConvertible> ex2(m, "BlockDataNotConvertible"); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } catch (const BlockDataNotConvertible &e) { + // Set BlockDataNotConvertible as the active python error + throw std::invalid_argument(e.what()); + } + }); + + py::class_<IBlock, std::unique_ptr<IBlock, py::nodelete>> (m, "Block") + .def ( "__getitem__", &IBlock_getData, py::keep_alive<0, 1>() ) + .def_property_readonly( "atDomainMinBorder", &IBlock_atDomainMinBorder ) + .def_property_readonly( "atDomainMaxBorder", &IBlock_atDomainMaxBorder ) + .def_property_readonly( "fieldNames", &IBlock_fieldNames ) + .def_property_readonly( "id", &IBlock_getIntegerID ) + .def ( "__hash__", &IBlock_getIntegerID ) + .def ( "__eq__", &IBlock_equals ) + .def ( "__repr__", &IBlock_str ) + .def_property_readonly("aabb", &IBlock::getAABB ) + ; + +} + +//====================================================================================================================== +// +// Logging & Abort +// +//====================================================================================================================== + + +static void wlb_log_devel ( const std::string & msg ) { WALBERLA_LOG_DEVEL ( msg ); } +static void wlb_log_devel_on_root ( const std::string & msg ) { WALBERLA_LOG_DEVEL_ON_ROOT ( msg ); } + +static void wlb_log_result ( const std::string & msg ) { WALBERLA_LOG_RESULT ( msg ); } +static void wlb_log_result_on_root ( const std::string & msg ) { WALBERLA_LOG_RESULT_ON_ROOT ( msg ); } + +static void wlb_log_warning ( const std::string & msg ) { WALBERLA_LOG_WARNING ( msg ); } +static void wlb_log_warning_on_root ( const std::string & msg ) { WALBERLA_LOG_WARNING_ON_ROOT ( msg ); } + +#ifdef WALBERLA_LOGLEVEL_INFO +static void wlb_log_info ( const std::string & msg ) { WALBERLA_LOG_INFO ( msg ); } +static void wlb_log_info_on_root ( const std::string & msg ) { WALBERLA_LOG_INFO_ON_ROOT ( msg ); } +#else +static void wlb_log_info ( const std::string & ) {} +static void wlb_log_info_on_root ( const std::string & ) {} +#endif + +#ifdef WALBERLA_LOGLEVEL_PROGRESS +static void wlb_log_progress ( const std::string & msg ) { WALBERLA_LOG_PROGRESS ( msg ); } +static void wlb_log_progress_on_root ( const std::string & msg ) { WALBERLA_LOG_PROGRESS_ON_ROOT( msg ); } +#else +static void wlb_log_progress ( const std::string & ) {} +static void wlb_log_progress_on_root ( const std::string & ) {} +#endif + +#ifdef WALBERLA_LOGLEVEL_DETAIL +static void wlb_log_detail ( const std::string & msg ) { WALBERLA_LOG_DETAIL ( msg ); } +static void wlb_log_detail_on_root ( const std::string & msg ) { WALBERLA_LOG_DETAIL_ON_ROOT ( msg ); } +#else +static void wlb_log_detail ( const std::string & ) {} +static void wlb_log_detail_on_root ( const std::string & ) {} +#endif + +static void wlb_abort ( const std::string & msg ) { WALBERLA_ABORT_NO_DEBUG_INFO ( msg ); } + +void exportLogging(py::module_ &m) +{ + m.def ( "log_devel" , wlb_log_devel ); + m.def ( "log_devel_on_root" , wlb_log_devel_on_root ); + m.def ( "log_result", wlb_log_result ); + m.def ( "log_result_on_root", wlb_log_result_on_root ); + m.def ( "log_warning", wlb_log_warning ); + m.def ( "log_warning_on_root", wlb_log_warning_on_root ); + m.def ( "log_info", wlb_log_info ); + m.def ( "log_info_on_root", wlb_log_info_on_root ); + m.def ( "log_progress", wlb_log_progress ); + m.def ( "log_progress_on_root",wlb_log_progress_on_root); + m.def ( "log_detail", wlb_log_detail ); + m.def ( "log_detail_on_root", wlb_log_detail_on_root ); + + m.def ( "abort", wlb_abort ); +} + +//====================================================================================================================== +// +// Communication +// +//====================================================================================================================== + +void exportCommunication(py::module_ &m) +{ + using communication::UniformPackInfo; + py::class_< UniformPackInfo, shared_ptr<UniformPackInfo>> //NOLINT + (m, "UniformPackInfo" ); + + using communication::UniformMPIDatatypeInfo; + py::class_< UniformMPIDatatypeInfo, shared_ptr<UniformMPIDatatypeInfo>> + (m, "UniformMPIDatatypeInfo" ); + +} + +//====================================================================================================================== +// +// Stencil Directions +// +//====================================================================================================================== + +void exportStencilDirections(py::module_ &m) +{ + + py::module_ m2 = m.def_submodule("stencil", "Stencil Extension of the waLBerla python bindings"); + py::enum_< stencil::Direction >(m2, "Direction") + .value("C", stencil::C) + .value("N", stencil::N) + .value("S", stencil::S) + .value("W", stencil::W) + .value("E", stencil::E) + .value("T", stencil::T) + .value("B", stencil::B) + .value("NW", stencil::NW) + .value("NE", stencil::NE) + .value("SW", stencil::SW) + .value("SE", stencil::SE) + .value("TN", stencil::TN) + .value("TS", stencil::TS) + .value("TW", stencil::TW) + .value("TE", stencil::TE) + .value("BN", stencil::BN) + .value("BS", stencil::BS) + .value("BW", stencil::BW) + .value("BE", stencil::BE) + .value("TNE", stencil::TNE) + .value("TNW", stencil::TNW) + .value("TSE", stencil::TSE) + .value("TSW", stencil::TSW) + .value("BNE", stencil::BNE) + .value("BNW", stencil::BNW) + .value("BSE", stencil::BSE) + .value("BSW", stencil::BSW) + .export_values(); + py::list cx; + + py::list cy; + + py::list cz; + + py::list dirStrings; + for (uint_t i = 0; i < stencil::NR_OF_DIRECTIONS; ++i) + { + cx.append(stencil::cx[i]); + cy.append(stencil::cy[i]); + cz.append(stencil::cz[i]); + dirStrings.append(stencil::dirToString[i]); + } + py::list c; + c.append(cx); + c.append(cy); + c.append(cz); + + m2.attr("cx") = cx; + m2.attr("cy") = cy; + m2.attr("cz") = cz; + m2.attr("c") = c; + m2.attr("dirStrings") = dirStrings; +} + + +//====================================================================================================================== +// +// Build Info +// +//====================================================================================================================== + + +void exportBuildInfo(py::module_ &m) +{ + py::module_ m2 = m.def_submodule("build_info", "Get waLBerla Build Information"); + m2.attr("version") = WALBERLA_GIT_SHA1; + m2.attr("type" ) = WALBERLA_BUILD_TYPE; + m2.attr("compiler_flags" ) = WALBERLA_COMPILER_FLAGS; + m2.attr("build_machine" ) = WALBERLA_BUILD_MACHINE; + m2.attr("source_dir") = WALBERLA_SOURCE_DIR; + m2.attr("build_dir") = WALBERLA_BUILD_DIR; +} + + + + +void exportBasicWalberlaDatastructures(py::module_ &m) +{ + exportMPI(m); + + exportBuildInfo(m); + exportCell(m); + exportCellInterval(m); + exportAABB(m); + + exportTiming(m); + + exportIBlock(m); + exportCommunication(m); + + exportLogging(m); + exportStencilDirections(m); + + // Add empty callbacks module + m.def_submodule("callbacks", "Empty callbacks module. Needed for the Szenario manager"); + +} + +} // namespace python_coupling +} // namespace walberla + +#endif + diff --git a/src/python_coupling/basic_exports/BasicExports.h b/src/python_coupling/export/BasicExport.h similarity index 87% rename from src/python_coupling/basic_exports/BasicExports.h rename to src/python_coupling/export/BasicExport.h index 27421cd1f..ba9bb2b07 100644 --- a/src/python_coupling/basic_exports/BasicExports.h +++ b/src/python_coupling/export/BasicExport.h @@ -16,18 +16,21 @@ //! \file BasicExports.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #pragma once +#include <pybind11/pybind11.h> namespace walberla { namespace python_coupling { +namespace py = pybind11; - void exportBasicWalberlaDatastructures(); + void exportBasicWalberlaDatastructures(py::module_ &m); diff --git a/src/blockforest/python/CommunicationExport.h b/src/python_coupling/export/BlockForestCommunicationExport.h similarity index 78% rename from src/blockforest/python/CommunicationExport.h rename to src/python_coupling/export/BlockForestCommunicationExport.h index f47406da0..f74ff2fa3 100644 --- a/src/blockforest/python/CommunicationExport.h +++ b/src/python_coupling/export/BlockForestCommunicationExport.h @@ -16,10 +16,12 @@ //! \file CommunicationExport.h //! \ingroup blockforest //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #pragma once +#include <pybind11/pybind11.h> @@ -27,15 +29,15 @@ namespace walberla { namespace blockforest { - template<typename Stencils> - void exportUniformBufferedScheme(); + template<typename... Stencils> + void exportUniformBufferedScheme(pybind11::module_& m); - template<typename Stencils> - void exportUniformDirectScheme(); + template<typename... Stencils> + void exportUniformDirectScheme(pybind11::module_& m); } // namespace blockforest } // namespace walberla -#include "CommunicationExport.impl.h" +#include "BlockForestCommunicationExport.impl.h" diff --git a/src/python_coupling/export/BlockForestCommunicationExport.impl.h b/src/python_coupling/export/BlockForestCommunicationExport.impl.h new file mode 100644 index 000000000..a45abe4ff --- /dev/null +++ b/src/python_coupling/export/BlockForestCommunicationExport.impl.h @@ -0,0 +1,242 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file CommunicationExport.impl.h +//! \ingroup blockforest +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#include "blockforest/communication/UniformBufferedScheme.h" +#include "blockforest/communication/UniformDirectScheme.h" + +#include "python_coupling/helper/MplHelpers.h" + +#include <pybind11/pybind11.h> + +namespace walberla +{ +namespace blockforest +{ +namespace py = pybind11; +namespace internal +{ +//=================================================================================================================== +// +// UniformBufferedScheme +// +//=================================================================================================================== + +/// the purpose of this class could also be solved by adding return_internal_reference to "createUniformDirectScheme" +/// however this is not easily possible since it returns not a reference but an py::object +template< typename Stencil > +class UniformBufferedSchemeWrapper : public blockforest::communication::UniformBufferedScheme< Stencil > +{ + public: + UniformBufferedSchemeWrapper(const shared_ptr< StructuredBlockForest >& bf, const int tag) + : blockforest::communication::UniformBufferedScheme< Stencil >(bf, tag), blockforest_(bf) + {} + + private: + shared_ptr< StructuredBlockForest > blockforest_; +}; + +struct UniformBufferedSchemeExporter +{ + UniformBufferedSchemeExporter(py::module_& m) : m_(m) {} + template< typename Stencil > + void operator()(python_coupling::NonCopyableWrap< Stencil >) const + { + typedef UniformBufferedSchemeWrapper< Stencil > UBS; + std::string class_name = "UniformBufferedScheme" + std::string(Stencil::NAME); + + py::class_< UBS, shared_ptr< UBS > >(m_, class_name.c_str()) + .def("__call__", &UBS::operator()) + .def("communicate", &UBS::communicate) + .def("startCommunication", &UBS::startCommunication) + .def("wait", &UBS::wait) + .def("addPackInfo", &UBS::addPackInfo) + .def("addDataToCommunicate", &UBS::addDataToCommunicate) + .def("localMode", &UBS::localMode) + .def("setLocalMode", &UBS::setLocalMode); + } + const py::module_& m_; +}; + +class UniformBufferedSchemeCreator +{ + public: + UniformBufferedSchemeCreator( const shared_ptr<StructuredBlockForest> & bf, + const std::string & stencilName, + const int tag ) + : blockforest_( bf), stencilName_( stencilName ), tag_( tag ) + {} + + template<typename Stencil> + void operator() ( python_coupling::NonCopyableWrap<Stencil> ) + { + + if ( std::string(Stencil::NAME) == stencilName_ ) { + result_ = py::cast( make_shared< UniformBufferedSchemeWrapper<Stencil> > ( blockforest_, tag_ ) ); + } + } + + py::object getResult() { return result_; } + private: + py::object result_; + shared_ptr<StructuredBlockForest> blockforest_; + std::string stencilName_; + const int tag_; +}; + + +template<typename... Stencils> +py::object createUniformBufferedScheme( const shared_ptr<StructuredBlockForest> & bf, + const std::string & stencil, const int tag ) +{ + UniformBufferedSchemeCreator creator( bf, stencil, tag ); + python_coupling::for_each_noncopyable_type< Stencils... > ( std::ref(creator) ); + + if ( !creator.getResult() ) + { + throw py::value_error("Unknown stencil."); + } + return creator.getResult(); +} + +//=================================================================================================================== +// +// UniformDirectScheme +// +//=================================================================================================================== + +template< typename Stencil > +class UniformDirectSchemeWrapper : public blockforest::communication::UniformDirectScheme< Stencil > +{ + public: + UniformDirectSchemeWrapper(const shared_ptr< StructuredBlockForest >& bf, const int tag) + : blockforest::communication::UniformDirectScheme< Stencil >( + bf, shared_ptr< walberla::communication::UniformMPIDatatypeInfo >(), tag), + blockforest_(bf) + {} + + private: + shared_ptr< StructuredBlockForest > blockforest_; +}; + +struct UniformDirectSchemeExporter +{ + UniformDirectSchemeExporter(py::module_& m) : m_(m) {} + template< typename Stencil > + void operator()(python_coupling::NonCopyableWrap< Stencil >) const + { + typedef UniformDirectSchemeWrapper< Stencil > UDS; + std::string class_name = "UniformDirectScheme_" + std::string(Stencil::NAME); + + py::class_< UDS, shared_ptr<UDS> >(m_, class_name.c_str() ) + .def("__call__", &UDS::operator()) + .def("communicate", &UDS::communicate) + .def("startCommunication", &UDS::startCommunication) + .def("wait", &UDS::wait) + .def("addDataToCommunicate", &UDS::addDataToCommunicate); + } + const py::module_ m_; +}; + +class UniformDirectSchemeCreator +{ + public: + UniformDirectSchemeCreator( const shared_ptr<StructuredBlockForest> & bf, + const std::string & stencilName, + const int tag ) + : blockforest_( bf), stencilName_( stencilName ), tag_( tag ) + {} + + template<typename Stencil> + void operator() ( python_coupling::NonCopyableWrap<Stencil> ) + { + + if ( std::string(Stencil::NAME) == stencilName_ ) { + result_ = py::cast( make_shared< UniformDirectSchemeWrapper<Stencil> > ( blockforest_, tag_ ) ); + } + } + + py::object getResult() { return result_; } + private: + py::object result_; + shared_ptr<StructuredBlockForest> blockforest_; + std::string stencilName_; + const int tag_; +}; + + +template<typename... Stencils> +py::object createUniformDirectScheme( const shared_ptr<StructuredBlockForest> & bf, + const std::string & stencil, const int tag ) +{ + UniformDirectSchemeCreator creator( bf, stencil, tag ); + python_coupling::for_each_noncopyable_type< Stencils... > ( std::ref(creator) ); + + if ( !creator.getResult() ) + { + throw py::value_error("Unknown stencil."); + } + return creator.getResult(); +} + + + + +} // namespace internal + +template< typename... Stencils > +void exportUniformDirectScheme(py::module_& m) +{ + using namespace py; + + python_coupling::for_each_noncopyable_type< Stencils... >(internal::UniformDirectSchemeExporter(m)); + m.def( "createUniformDirectScheme", + [](const shared_ptr<StructuredBlockForest> & blocks, const std::string & stencil, const int tag) + { + return internal::createUniformDirectScheme< Stencils... >(blocks, stencil, tag); + }, + "blocks"_a, "stencil"_a, "tag"_a=778 ); + + +} + +template< typename... Stencils > +void exportUniformBufferedScheme(py::module_& m) +{ + using namespace py; + + py::enum_< LocalCommunicationMode >(m, "LocalCommunicationMode") + .value("START", START) + .value("WAIT", WAIT) + .value("BUFFER", BUFFER) + .export_values(); + + python_coupling::for_each_noncopyable_type< Stencils... >(internal::UniformBufferedSchemeExporter(m)); + m.def( "createUniformBufferedScheme", + [](const shared_ptr<StructuredBlockForest> & blocks, const std::string & stencil, const int tag) + { + return internal::createUniformBufferedScheme< Stencils... >(blocks, stencil, tag); + }, + "blocks"_a, "stencil"_a, "tag"_a=778 ); +} + +} // namespace blockforest +} // namespace walberla \ No newline at end of file diff --git a/src/python_coupling/export/BlockForestExport.cpp b/src/python_coupling/export/BlockForestExport.cpp new file mode 100644 index 000000000..14232baae --- /dev/null +++ b/src/python_coupling/export/BlockForestExport.cpp @@ -0,0 +1,340 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file Exports.cpp +//! \ingroup blockforest +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +// Do not reorder includes - the include order is important +#include "python_coupling/PythonWrapper.h" + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +# include "blockforest/Initialization.h" +# include "blockforest/SetupBlock.h" +# include "blockforest/SetupBlockForest.h" +# include "blockforest/StructuredBlockForest.h" + +# include "core/StringUtility.h" +# include "core/mpi/MPIIO.h" + +# include "stencil/D3Q19.h" + +# include <memory> +# include <pybind11/stl.h> +# include <sstream> + +# include "BlockForestExport.h" +# include "python_coupling/helper/OwningIterator.h" + +namespace walberla +{ +namespace blockforest +{ +std::string printSetupBlock(const SetupBlock& b) +{ + std::stringstream out; + out << "SetupBlock at " << b.getAABB(); + return out.str(); +} + +namespace py = pybind11; + +//====================================================================================================================== +// +// StructuredBlockForest +// +//====================================================================================================================== + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +void NoSuchBlockData::translate( const NoSuchBlockData & e ) { + throw py::cast_error(e.what()); +} + +void BlockDataNotConvertible::translate( const BlockDataNotConvertible & e ) { + throw py::cast_error(e.what()); +} +#else + +void NoSuchBlockData::translate( const NoSuchBlockData & ) {} + +void BlockDataNotConvertible::translate( const BlockDataNotConvertible & ) {} + +#endif + +BlockDataID blockDataIDFromString(BlockStorage& bs, const std::string& stringID) +{ + auto ids = bs.getBlockDataIdentifiers(); + + for (uint_t i = 0; i < ids.size(); ++i) + if (ids[i] == stringID) return BlockDataID(i); + + throw NoSuchBlockData(); +} + +BlockDataID blockDataIDFromString(IBlock& block, const std::string& stringID) +{ + return blockDataIDFromString(block.getBlockStorage(), stringID); +} + +BlockDataID blockDataIDFromString(StructuredBlockForest& bs, const std::string& stringID) +{ + return blockDataIDFromString(bs.getBlockStorage(), stringID); +} + +py::iterator StructuredBlockForest_iter(const shared_ptr< StructuredBlockForest >& bf) // NOLINT +{ + // shared_ptr<StructuredBlockForest> s = py::cast< shared_ptr<StructuredBlockForest> > ( StructuredBlockForest ); + + std::vector< const IBlock* > blocks; + bf->getBlocks(blocks); + std::vector<py::object> resultList; + resultList.reserve(blocks.size()); + + for (auto it = blocks.begin(); it != blocks.end(); ++it) + { + py::object theObject = py::cast(*it); + resultList.push_back(theObject); + } + + return python_coupling::make_owning_iterator(resultList); +} + +py::object StructuredBlockForest_getItem(const shared_ptr< StructuredBlockForest >& bf, uint_t i) // NOLINT +{ + if (i >= bf->size()) { throw py::value_error("Index out of bounds"); } + + std::vector< const IBlock* > blocks; + bf->getBlocks(blocks); + + py::object theObject = py::cast(blocks[i]); + return theObject; +} + +std::vector<py::object> StructuredBlockForest_blocksOverlappedByAABB(StructuredBlockForest& s, const AABB& aabb) +{ + std::vector< IBlock* > blocks; + s.getBlocksOverlappedByAABB(blocks, aabb); + + std::vector<py::object> resultList; + for (auto it = blocks.begin(); it != blocks.end(); ++it) + resultList.push_back(py::cast(*it)); + return resultList; +} + +std::vector<py::object> StructuredBlockForest_blocksContainedWithinAABB(StructuredBlockForest& s, const AABB& aabb) +{ + std::vector< IBlock* > blocks; + s.getBlocksContainedWithinAABB(blocks, aabb); + + std::vector<py::object> resultList; + for (auto it = blocks.begin(); it != blocks.end(); ++it) + resultList.push_back(py::cast(*it)); + return resultList; +} + +py::object SbF_transformGlobalToLocal(StructuredBlockForest& s, IBlock& block, const py::object& global) +{ + if (py::isinstance< CellInterval >(global)) + { + CellInterval ret; + s.transformGlobalToBlockLocalCellInterval(ret, block, py::cast< CellInterval >(global)); + return py::cast(ret); + } + else if (py::isinstance< Cell >(global)) + { + Cell ret; + s.transformGlobalToBlockLocalCell(ret, block, py::cast< Cell >(global)); + return py::cast(ret); + } + + throw py::value_error("Only CellIntervals and cells can be transformed"); +} + +py::object SbF_transformLocalToGlobal(StructuredBlockForest& s, IBlock& block, const py::object& local) +{ + if (py::isinstance< CellInterval >(local)) + { + CellInterval ret; + s.transformBlockLocalToGlobalCellInterval(ret, block, py::cast< CellInterval >(local)); + return py::cast(ret); + } + else if (py::isinstance< Cell >(local)) + { + Cell ret; + s.transformBlockLocalToGlobalCell(ret, block, py::cast< Cell >(local)); + return py::cast(ret); + } + throw py::value_error("Only CellIntervals and cells can be transformed"); +} + +void SbF_writeBlockData(StructuredBlockForest& s, const std::string& blockDataId, const std::string& file) +{ + mpi::SendBuffer buffer; + s.serializeBlockData(blockDataIDFromString(s, blockDataId), buffer); + mpi::writeMPIIO(file, buffer); +} + +void SbF_readBlockData(StructuredBlockForest& s, const std::string& blockDataId, const std::string& file) +{ + mpi::RecvBuffer buffer; + mpi::readMPIIO(file, buffer); + + s.deserializeBlockData(blockDataIDFromString(s, blockDataId), buffer); + if (!buffer.isEmpty()) + { throw py::cast_error("Reading failed - file does not contain matching data for this type."); } +} + +CellInterval SbF_getBlockCellBB(StructuredBlockForest& s, const IBlock* block) { return s.getBlockCellBB(*block); } + +std::array<real_t , 3> SbF_mapToPeriodicDomain1(StructuredBlockForest& s, real_t x, real_t y, real_t z) +{ + Vector3< real_t > res(x, y, z); + s.mapToPeriodicDomain(res); + return std::array< real_t, 3 >{ res[0], res[1], res[2] }; +} + +std::array<real_t , 3> SbF_mapToPeriodicDomain2(StructuredBlockForest& s, const std::array<real_t, 3>& in) +{ + Vector3< real_t > tmp(in[0], in[1], in[2]); + s.mapToPeriodicDomain(tmp); + return std::array< real_t, 3 >{ tmp[0], tmp[1], tmp[2] }; +} + +Cell SbF_mapToPeriodicDomain3(StructuredBlockForest& s, Cell in, uint_t level = 0) +{ + s.mapToPeriodicDomain(in, level); + return in; +} + +py::object SbF_getBlock1(StructuredBlockForest& s, const real_t x, const real_t y, const real_t z) +{ + return py::cast(s.getBlock(x, y, z)); +} + +py::object SbF_getBlock2(StructuredBlockForest& s, const std::array<real_t, 3>& v) +{ + return py::cast(s.getBlock(Vector3<real_t>(v[0], v[1], v[2]))); + +} + +py::tuple SbF_periodic(StructuredBlockForest& s) +{ + return py::make_tuple(s.isXPeriodic(), s.isYPeriodic(), s.isZPeriodic()); +} + +bool p_blockExists1(StructuredBlockForest& s, const std::array<real_t, 3>& v) +{ + return s.blockExists(Vector3<real_t>(v[0], v[1], v[2])); + +} + +bool p_blockExistsLocally1(StructuredBlockForest& s, const std::array<real_t, 3>& v) +{ + return s.blockExistsLocally(Vector3<real_t>(v[0], v[1], v[2])); + +} + +bool p_blockExistsRemotely1(StructuredBlockForest& s, const std::array<real_t, 3>& v) +{ + return s.blockExistsRemotely(Vector3<real_t>(v[0], v[1], v[2])); + +} + +bool SbF_atDomainXMinBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainXMinBorder(*b); } +bool SbF_atDomainXMaxBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainXMaxBorder(*b); } +bool SbF_atDomainYMinBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainYMinBorder(*b); } +bool SbF_atDomainYMaxBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainYMaxBorder(*b); } +bool SbF_atDomainZMinBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainZMinBorder(*b); } +bool SbF_atDomainZMaxBorder(StructuredBlockForest& s, const IBlock* b) { return s.atDomainZMaxBorder(*b); } + +void exportBlockForest(py::module_& m) +{ + using namespace pybind11::literals; + + bool (StructuredBlockForest::*p_blockExists2)(const real_t, const real_t, const real_t) const = + &StructuredBlockForest::blockExists; + bool (StructuredBlockForest::*p_blockExistsLocally2)(const real_t, const real_t, const real_t) const = + &StructuredBlockForest::blockExistsLocally; + bool (StructuredBlockForest::*p_blockExistsRemotely2)(const real_t, const real_t, const real_t) const = + &StructuredBlockForest::blockExistsRemotely; + + py::class_< StructuredBlockForest, std::shared_ptr< StructuredBlockForest > >(m, "StructuredBlockForest") + .def("getNumberOfLevels", &StructuredBlockForest::getNumberOfLevels) + .def_property_readonly("getDomain", &StructuredBlockForest::getDomain) + .def("mapToPeriodicDomain", &SbF_mapToPeriodicDomain1) + .def("mapToPeriodicDomain", &SbF_mapToPeriodicDomain2) + .def("mapToPeriodicDomain", &SbF_mapToPeriodicDomain3) + .def("__getitem__", &StructuredBlockForest_getItem, py::keep_alive< 0, 1 >()) + .def("__len__", &StructuredBlockForest::size) + .def("getBlock", SbF_getBlock1, py::keep_alive< 0, 1 >()) + .def("getBlock", SbF_getBlock2, py::keep_alive< 0, 1 >()) + .def("containsGlobalBlockInformation", &StructuredBlockForest::containsGlobalBlockInformation) + .def("blocksOverlappedByAABB", &StructuredBlockForest_blocksOverlappedByAABB, py::keep_alive< 0, 1 >()) + .def("blocksContainedWithinAABB", &StructuredBlockForest_blocksContainedWithinAABB, py::keep_alive< 0, 1 >()) + .def("blockExists", &p_blockExists1) + .def("blockExists", p_blockExists2) + .def("blockExistsLocally", &p_blockExistsLocally1) + .def("blockExistsLocally", p_blockExistsLocally2) + .def("blockExistsRemotely", &p_blockExistsRemotely1) + .def("blockExistsRemotely", p_blockExistsRemotely2) + .def("atDomainXMinBorder", &SbF_atDomainXMinBorder) + .def("atDomainXMaxBorder", &SbF_atDomainXMaxBorder) + .def("atDomainYMinBorder", &SbF_atDomainYMinBorder) + .def("atDomainYMaxBorder", &SbF_atDomainYMaxBorder) + .def("atDomainZMinBorder", &SbF_atDomainZMinBorder) + .def("atDomainZMaxBorder", &SbF_atDomainZMaxBorder) + .def("dx", &StructuredBlockForest::dx) + .def("dy", &StructuredBlockForest::dy) + .def("dz", &StructuredBlockForest::dz) + .def("getDomainCellBB", &StructuredBlockForest::getDomainCellBB, "level"_a=0) + .def("getBlockCellBB", &SbF_getBlockCellBB) + .def("transformGlobalToLocal", &SbF_transformGlobalToLocal) + .def("transformLocalToGlobal", &SbF_transformLocalToGlobal) + .def("writeBlockData", &SbF_writeBlockData) + .def("readBlockData", &SbF_readBlockData) + .def("__iter__", &StructuredBlockForest_iter, py::keep_alive< 0, 1 >()) + .def_property_readonly("containsGlobalBlockInformation", &StructuredBlockForest::containsGlobalBlockInformation) + .def_property_readonly("periodic", &SbF_periodic); + + py::class_< SetupBlock, shared_ptr< SetupBlock > >(m, "SetupBlock") + .def("get_level", &SetupBlock::getLevel) + .def("set_workload", &SetupBlock::setWorkload) + .def("get_workload", &SetupBlock::getWorkload) + .def("set_memory", &SetupBlock::setMemory) + .def("get_memory", &SetupBlock::getMemory) + .def("get_aabb", &SetupBlock::getAABB) + .def("__repr__", &printSetupBlock); + + m.def( + "createUniformBlockGrid", + [](std::array< uint_t, 3 > blocks, std::array< uint_t, 3 > cellsPerBlock, real_t dx, + bool oneBlockPerProcess, std::array< bool, 3 > periodic, bool keepGlobalBlockInformation) { + return blockforest::createUniformBlockGrid(blocks[0], blocks[1], blocks[2], cellsPerBlock[0], cellsPerBlock[1], + cellsPerBlock[2], dx, oneBlockPerProcess, periodic[0], periodic[1], periodic[2], + keepGlobalBlockInformation); + }, + "blocks"_a, "cellsPerBlock"_a, "dx"_a = real_t(1), "oneBlockPerProcess"_a = true, + "periodic"_a = std::array< bool, 3 >{ false, false, false }, "keepGlobalBlockInformation"_a = false); +} + +} // namespace blockforest +} // namespace walberla + +#endif // WALBERLA_BUILD_WITH_PYTHON \ No newline at end of file diff --git a/src/python_coupling/export/BlockForestExport.h b/src/python_coupling/export/BlockForestExport.h new file mode 100644 index 000000000..1c5ddaae4 --- /dev/null +++ b/src/python_coupling/export/BlockForestExport.h @@ -0,0 +1,69 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file Exports.h +//! \ingroup blockforest +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "waLBerlaDefinitions.h" + + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +# include <pybind11/pybind11.h> + +# include "BlockForestCommunicationExport.h" + +namespace walberla { +namespace blockforest { + + struct NoSuchBlockData : public std::out_of_range { + explicit NoSuchBlockData ( ) : std::out_of_range( "No blockdata with the given name found" ) {} + explicit NoSuchBlockData ( const std::string & w ) : std::out_of_range(w) {} + static void translate( const NoSuchBlockData & e ); + }; + struct BlockDataNotConvertible : public std::runtime_error { + explicit BlockDataNotConvertible ( ) : std::runtime_error( "This blockdata is not accessible from Python" ) {} + explicit BlockDataNotConvertible ( const std::string & w ) : std::runtime_error(w) {} + static void translate( const BlockDataNotConvertible & e ); + }; + + BlockDataID blockDataIDFromString( IBlock & block, const std::string & stringID ); + BlockDataID blockDataIDFromString( BlockStorage & bs, const std::string & stringID ); + BlockDataID blockDataIDFromString( StructuredBlockStorage & bs, const std::string & stringID ); + + namespace py = pybind11; + + void exportBlockForest(py::module_ &m); + + + template<typename... Stencils> + void exportModuleToPython(py::module_ &m) + { + exportBlockForest(m); + exportUniformBufferedScheme<Stencils...>(m); + exportUniformDirectScheme<Stencils...>(m); + } + +} // namespace blockforest +} // namespace walberla + + +#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/cuda/python/Exports.h b/src/python_coupling/export/CUDAExport.h similarity index 81% rename from src/cuda/python/Exports.h rename to src/python_coupling/export/CUDAExport.h index 5e5b00090..505aa3368 100644 --- a/src/cuda/python/Exports.h +++ b/src/python_coupling/export/CUDAExport.h @@ -13,9 +13,10 @@ // You should have received a copy of the GNU General Public License along // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // -//! \file FieldExport.h +//! \file CUDAExport.h //! \ingroup cuda //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -28,14 +29,16 @@ namespace walberla { namespace cuda { - template<typename GpuFields, typename CpuFields> - void exportModuleToPython(); + template<typename... GpuFields> + void exportModuleToPython(py::module_ &m); + + template<typename... CpuFields> + void exportCopyFunctionsToPython(py::module_ &m); } // namespace cuda } // namespace walberla -#include "Exports.impl.h" - +#include "CUDAExport.impl.h" #endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/python_coupling/export/CUDAExport.impl.h b/src/python_coupling/export/CUDAExport.impl.h new file mode 100644 index 000000000..eb60759cb --- /dev/null +++ b/src/python_coupling/export/CUDAExport.impl.h @@ -0,0 +1,394 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file CUDAExport.impl.h +//! \ingroup cuda +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +// Do not reorder includes - the include order is important +#include "core/logging/Logging.h" + +#include "cuda/AddGPUFieldToStorage.h" +#include "cuda/FieldCopy.h" +#include "cuda/GPUField.h" +#include "cuda/communication/GPUPackInfo.h" + +#include "field/AddToStorage.h" +#include "field/communication/UniformMPIDatatypeInfo.h" + +#include "python_coupling/PythonWrapper.h" +#include "python_coupling/helper/MplHelpers.h" + +namespace walberla { +namespace cuda { + + + +namespace internal { +using namespace pybind11::literals; + //=================================================================================================================== + // + // Field export + // + //=================================================================================================================== + + template<typename GpuField_T> + uint64_t gpufield_ptr(const GpuField_T & gpuField) + { + return reinterpret_cast<uint64_t>(gpuField.pitchedPtr().ptr); + // return gpuField.pitchedPtr(); + } + + template<typename GpuField_T> + std::string gpufield_dtypeStr(const GpuField_T & ) + { + return std::string(field::internal::PythonFormatString<typename GpuField_T::value_type>::get()); + } + + struct GpuFieldExporter + { + GpuFieldExporter(py::module_& m) : m_(m) {} + template< typename GpuField_T> + void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) const + { + + typedef typename GpuField_T::value_type T; + std::string data_type_name = field::internal::PythonFormatString<T>::get(); + + std::string class_name = "GpuField_" + data_type_name; + py::class_<GpuField_T, shared_ptr<GpuField_T>>(m_, class_name.c_str() ) + .def_property_readonly("layout", &field::internal::field_layout < GpuField_T > ) + .def_property_readonly("size", &field::internal::field_size < GpuField_T > ) + .def_property_readonly("sizeWithGhostLayers", &field::internal::field_sizeWithGhostLayer< GpuField_T > ) + .def_property_readonly("allocSize", &field::internal::field_allocSize < GpuField_T > ) + .def_property_readonly("strides", &field::internal::field_strides < GpuField_T > ) + .def_property_readonly("offsets", &field::internal::field_offsets < GpuField_T > ) + .def_property_readonly("ptr", &gpufield_ptr < GpuField_T > ) + .def_property_readonly("dtypeStr", &gpufield_dtypeStr < GpuField_T > ) + .def_property_readonly("isPitchedMem", &GpuField_T::isPitchedMem ) + .def("swapDataPointers", &field::internal::field_swapDataPointers < GpuField_T > ) + .def_property_readonly("nrOfGhostLayers", &GpuField_T::nrOfGhostLayers ) + .def("cloneUninitialized", &GpuField_T::cloneUninitialized, py::return_value_policy::copy) + ; + + + using field::communication::PackInfo; + using communication::GPUPackInfo; + std::string GpuFieldPackInfoName = "GpuFieldPackInfo_" + data_type_name; + py::class_< GPUPackInfo<GpuField_T>, shared_ptr< GPUPackInfo<GpuField_T> >, walberla::communication::UniformPackInfo>(m_, GpuFieldPackInfoName.c_str() ); + + using field::communication::UniformMPIDatatypeInfo; + std::string GpuFieldMPIDataTypeInfoName = "GpuFieldMPIDataTypeInfo_" + data_type_name; + py::class_< UniformMPIDatatypeInfo<GpuField_T>, shared_ptr< UniformMPIDatatypeInfo<GpuField_T> >, walberla::communication::UniformMPIDatatypeInfo>(m_, GpuFieldMPIDataTypeInfoName.c_str() ); + + } + const py::module_& m_; + }; + + + //=================================================================================================================== + // + // addToStorage + // + //=================================================================================================================== + + class AddToStorageExporter + { + public: + AddToStorageExporter( const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, + py::object &dtype, uint_t fs, uint_t gl, Layout layout, + bool usePitchedMem ) + : blocks_( blocks ), name_( name ), dtype_(dtype), fs_( fs ), + gl_(gl),layout_( layout), usePitchedMem_(usePitchedMem), found_(true) + {} + + template< typename GpuField_T> + void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) + { + typedef typename GpuField_T::value_type T; + + if(python_coupling::isCppEqualToPythonType<T>(py::cast<std::string>(dtype_.attr("__name__")))) + { + addGPUFieldToStorage< GPUField< T > >(blocks_, name_, fs_, layout_, gl_, usePitchedMem_); + } + } + + bool successful() const { return found_; } + private: + shared_ptr< StructuredBlockForest > blocks_; + std::string name_; + py::object dtype_; + uint_t fs_; + uint_t gl_; + Layout layout_; + bool usePitchedMem_; + bool found_; + }; + + template<typename... GpuFields> + void addToStorage( const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, py::object &dtype, + uint_t fs, uint_t gl, Layout layout, bool usePitchedMem ) + { + namespace py = pybind11; + auto result = make_shared<py::object>(); + AddToStorageExporter exporter( blocks, name, dtype, fs, gl, layout, usePitchedMem ); + python_coupling::for_each_noncopyable_type<GpuFields...>( std::ref(exporter) ); + } + + + //=================================================================================================================== + // + // createPackInfo Export + // + //=================================================================================================================== + + class PackInfoExporter + { + public: + PackInfoExporter(const shared_ptr<StructuredBlockForest> & blocks, BlockDataID fieldId, uint_t numberOfGhostLayers) + : blocks_(blocks), fieldId_(fieldId), numberOfGhostLayers_( numberOfGhostLayers ) + {} + + template< typename GpuField_T> + void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) + { + using cuda::communication::GPUPackInfo; + + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<GpuField_T>(fieldId_) ) + { + if ( numberOfGhostLayers_ > 0 ) + { + resultPackInfo_ = py::cast(make_shared< GPUPackInfo< GpuField_T > >(fieldId_, numberOfGhostLayers_)); + } + else + { + resultPackInfo_ = py::cast(make_shared< GPUPackInfo< GpuField_T > >(fieldId_)); + } + } + } + py::object getResultPackInfo() + { + return resultPackInfo_; + } + + private: + py::object resultPackInfo_; + shared_ptr< StructuredBlockStorage > blocks_; + BlockDataID fieldId_; + uint_t numberOfGhostLayers_; + }; + + + template<typename... GpuField_T> + static py::object PackInfoWrapper(const shared_ptr<StructuredBlockForest> & blocks, + const std::string & name, uint_t numberOfGhostLayers ) + { + using cuda::communication::GPUPackInfo; + BlockDataID fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + if ( blocks->begin() == blocks->end() ) { + // if no blocks are on this field an arbitrary PackInfo can be returned + return py::cast( make_shared< GPUPackInfo<GPUField<int8_t>> >( fieldID, numberOfGhostLayers ) ); + } + + PackInfoExporter exporter(blocks, fieldID, numberOfGhostLayers); + python_coupling::for_each_noncopyable_type< GpuField_T... > ( std::ref(exporter) ); + if ( ! exporter.getResultPackInfo() ) { + throw py::value_error("Failed to create GPU PackInfo"); + } + else { + return exporter.getResultPackInfo(); + } + } + + //=================================================================================================================== + // + // createMPIDatatypeInfo + // + //=================================================================================================================== + + class UniformMPIDatatypeInfoExporter + { + public: + UniformMPIDatatypeInfoExporter(const shared_ptr<StructuredBlockForest> & blocks, BlockDataID fieldId, uint_t numberOfGhostLayers) + : blocks_(blocks), fieldId_(fieldId), numberOfGhostLayers_( numberOfGhostLayers ) + {} + + template< typename GpuField_T> + void operator() ( python_coupling::NonCopyableWrap<GpuField_T> ) + { + using field::communication::UniformMPIDatatypeInfo; + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<GpuField_T>(fieldId_) ) + { + if ( numberOfGhostLayers_ > 0 ) + resultMPIDatatypeInfo_ = py::cast( make_shared< UniformMPIDatatypeInfo<GpuField_T> >( fieldId_, numberOfGhostLayers_ ) ); + else + resultMPIDatatypeInfo_ = py::cast( make_shared< UniformMPIDatatypeInfo<GpuField_T> >( fieldId_ ) ); + + } + } + py::object getResultUniformMPIDatatype() + { + return resultMPIDatatypeInfo_; + } + + private: + py::object resultMPIDatatypeInfo_; + shared_ptr< StructuredBlockStorage > blocks_; + BlockDataID fieldId_; + uint_t numberOfGhostLayers_; + }; + + + template<typename... GpuField_T> + static py::object UniformMPIDatatypeInfoWrapper(const shared_ptr<StructuredBlockForest> & blocks, + const std::string & name, uint_t numberOfGhostLayers ) + { + using field::communication::UniformMPIDatatypeInfo; + BlockDataID fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + if ( blocks->begin() == blocks->end() ) { + // if no blocks are on this field an arbitrary PackInfo can be returned + return py::cast( make_shared< UniformMPIDatatypeInfo<GPUField<int8_t>> >( fieldID, numberOfGhostLayers ) ); + } + + UniformMPIDatatypeInfoExporter exporter(blocks, fieldID, numberOfGhostLayers); + python_coupling::for_each_noncopyable_type< GpuField_T... > ( std::ref(exporter) ); + if ( ! exporter.getResultUniformMPIDatatype() ) { + throw py::value_error("Failed to create GPU UniformMPIDatatype"); + } + else { + return exporter.getResultUniformMPIDatatype(); + } + } + + //=================================================================================================================== + // + // fieldCopy + // + //=================================================================================================================== + +class copyFieldToGpuDispatchExporter +{ + public: + copyFieldToGpuDispatchExporter( const shared_ptr<StructuredBlockForest> & blocks, + BlockDataID gpuFieldId, BlockDataID cpuFieldId, bool toGPU) + : blocks_( blocks ), gpuFieldId_( gpuFieldId ), cpuFieldId_(cpuFieldId), toGPU_( toGPU ) + {} + + template< typename CpuField_T> + void operator() ( python_coupling::NonCopyableWrap<CpuField_T> ) + { + typedef cuda::GPUField<typename CpuField_T::value_type> GpuField_T; + IBlock * firstBlock = & ( * blocks_->begin() ); + + if(firstBlock->isDataClassOrSubclassOf< CpuField_T > ( cpuFieldId_ ) ) + { + if(toGPU_) + cuda::fieldCpy<GpuField_T, CpuField_T>(blocks_, gpuFieldId_, cpuFieldId_); + else + cuda::fieldCpy<CpuField_T, GpuField_T>(blocks_, cpuFieldId_, gpuFieldId_); + } + } + private: + shared_ptr< StructuredBlockForest > blocks_; + BlockDataID gpuFieldId_; + BlockDataID cpuFieldId_; + bool toGPU_; +}; + +template<typename... CpuFields> +void copyFieldToGPU(const shared_ptr< StructuredBlockForest > & blocks, const std::string & gpuFieldName, + const std::string & cpuFieldName, bool toGPU ) +{ + namespace py = pybind11; + auto result = make_shared<py::object>(); + + BlockDataID gpuFieldId = python_coupling::blockDataIDFromString( *blocks, gpuFieldName ); + BlockDataID cpuFieldId = python_coupling::blockDataIDFromString( *blocks, cpuFieldName ); + + copyFieldToGpuDispatchExporter exporter( blocks, gpuFieldId, cpuFieldId, toGPU ); + python_coupling::for_each_noncopyable_type<CpuFields...>( std::ref(exporter) ); +} +} // namespace internal + + +using namespace pybind11::literals; + +template<typename... GpuFields> +void exportModuleToPython(py::module_ &m) +{ + py::module_ m2 = m.def_submodule("cuda", "Cuda Extension of the waLBerla python bindings"); + + python_coupling::for_each_noncopyable_type<GpuFields...>( internal::GpuFieldExporter(m2) ); + + m2.def( + "addGpuFieldToStorage", + [](const shared_ptr< StructuredBlockForest > & blocks, const std::string & name, py::object &dtype, uint_t fSize, + bool usePitchedMem, uint_t ghostLayers, Layout layout) { + return internal::addToStorage<GpuFields...>(blocks, name, dtype, fSize, ghostLayers, layout, usePitchedMem); + }, + "blocks"_a, "name"_a, "dtype"_a, "fSize"_a=1, "usePitchedMem"_a=false, "ghostLayers"_a=uint(1), "layout"_a=zyxf); + + m2.def( + "createPackInfo", + [](const shared_ptr<StructuredBlockForest> & blocks, + const std::string & blockDataName, uint_t numberOfGhostLayers ) { + return internal::PackInfoWrapper< GpuFields... >(blocks, blockDataName, numberOfGhostLayers); + }, + "blocks"_a, "blockDataName"_a, "numberOfGhostLayers"_a = uint_t(0)); + + m2.def( + "createMPIDatatypeInfo", + [](const shared_ptr<StructuredBlockForest> & blocks, + const std::string & blockDataName, uint_t numberOfGhostLayers ) { + return internal::UniformMPIDatatypeInfoWrapper< GpuFields... >(blocks, blockDataName, numberOfGhostLayers); + }, + "blocks"_a, "blockDataName"_a, "numberOfGhostLayers"_a = uint_t(0)); + +} + +template<typename... CpuFields > +void exportCopyFunctionsToPython(py::module_ &m) +{ + py::module_ m2 = m.def_submodule("cuda", "Cuda Extension of the waLBerla python bindings"); + + m2.def( + "copyFieldToGpu", + [](const shared_ptr< StructuredBlockForest > & blocks, const std::string & gpuFieldName, const std::string & cpuFieldName) { + return internal::copyFieldToGPU<CpuFields...>(blocks, gpuFieldName, cpuFieldName, true); + }, + "blocks"_a, "gpuFieldName"_a, "cpuFieldName"_a); + + m2.def( + "copyFieldToCpu", + [](const shared_ptr< StructuredBlockForest > & blocks, const std::string & gpuFieldName, const std::string & cpuFieldName) { + return internal::copyFieldToGPU<CpuFields...>(blocks, gpuFieldName, cpuFieldName, false); + }, + "blocks"_a, "gpuFieldName"_a, "cpuFieldName"_a); +} + + + + +} // namespace cuda +} // namespace walberla + + diff --git a/src/field/python/CommunicationExport.h b/src/python_coupling/export/FieldCommunicationExport.h similarity index 85% rename from src/field/python/CommunicationExport.h rename to src/python_coupling/export/FieldCommunicationExport.h index 8b76146a6..ccbe61496 100644 --- a/src/field/python/CommunicationExport.h +++ b/src/python_coupling/export/FieldCommunicationExport.h @@ -16,6 +16,7 @@ //! \file CommunicationExport.h //! \ingroup field //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -26,17 +27,17 @@ namespace walberla { namespace field { +namespace py = pybind11; - template<typename FieldTypes> - void exportCommunicationClasses(); + template<typename... FieldTypes> + void exportCommunicationClasses(py::module_ &m); } // namespace field } // namespace walberla -#include "CommunicationExport.impl.h" - +#include "FieldCommunicationExport.impl.h" #endif //WALBERLA_BUILD_WITH_PYTHON diff --git a/src/python_coupling/export/FieldCommunicationExport.impl.h b/src/python_coupling/export/FieldCommunicationExport.impl.h new file mode 100644 index 000000000..0793b26a5 --- /dev/null +++ b/src/python_coupling/export/FieldCommunicationExport.impl.h @@ -0,0 +1,249 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file CommunicationExport.impl.h +//! \ingroup field +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#include "python_coupling/PythonWrapper.h" + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +# include "blockforest/StructuredBlockForest.h" +# include "python_coupling/helper/BlockStorageExportHelpers.h" + +# include "field/communication/PackInfo.h" +# include "field/communication/StencilRestrictedPackInfo.h" +# include "field/communication/UniformMPIDatatypeInfo.h" + +# include "python_coupling/helper/MplHelpers.h" + +# include "stencil/D2Q9.h" +# include "stencil/D3Q15.h" +# include "stencil/D3Q19.h" +# include "stencil/D3Q27.h" +# include "stencil/D3Q7.h" + +# include <typeinfo> + +# include "pybind11/pybind11.h" + +namespace walberla +{ +namespace field +{ +namespace internal +{ +namespace py = pybind11; + +//=================================================================================================================== +// +// createPackInfo Export +// +//=================================================================================================================== + +class PackInfoExporter +{ + public: + PackInfoExporter(const shared_ptr<StructuredBlockForest> & blocks, BlockDataID fieldId, uint_t numberOfGhostLayers) + : blocks_(blocks), fieldId_(fieldId), numberOfGhostLayers_( numberOfGhostLayers ) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) + { + typedef typename FieldType::value_type T; + const uint_t F_SIZE = FieldType::F_SIZE; + typedef GhostLayerField<T, F_SIZE> GlField_T; + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) + { + if ( numberOfGhostLayers_ > 0 ) + { + resultPackInfo_ = py::cast(make_shared< field::communication::PackInfo< GlField_T > >(fieldId_, numberOfGhostLayers_)); + } + else + { + resultPackInfo_ = py::cast(make_shared< field::communication::PackInfo< GlField_T > >(fieldId_)); + } + } + } + py::object getResultPackInfo() + { + return resultPackInfo_; + } + + private: + py::object resultPackInfo_; + shared_ptr< StructuredBlockStorage > blocks_; + BlockDataID fieldId_; + uint_t numberOfGhostLayers_; +}; + + +template<typename... FieldTypes> +static py::object PackInfoWrapper(const shared_ptr<StructuredBlockForest> & blocks, + const std::string & name, uint_t numberOfGhostLayers ) +{ + BlockDataID fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + if ( blocks->begin() == blocks->end() ) { + // if no blocks are on this field an arbitrary PackInfo can be returned + return py::cast( make_shared< field::communication::PackInfo<GhostLayerField<real_t,1>> >( fieldID, numberOfGhostLayers ) ); + } + + PackInfoExporter exporter(blocks, fieldID, numberOfGhostLayers); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( std::ref(exporter) ); + if ( ! exporter.getResultPackInfo() ) { + throw py::value_error("Failed to create PackInfo"); + } + else { + return exporter.getResultPackInfo(); + } +} + +//=================================================================================================================== +// +// createMPIDatatypeInfo +// +//=================================================================================================================== + +class UniformMPIDatatypeInfoExporter +{ + public: + UniformMPIDatatypeInfoExporter(const shared_ptr<StructuredBlockForest> & blocks, BlockDataID fieldId, uint_t numberOfGhostLayers) + : blocks_(blocks), fieldId_(fieldId), numberOfGhostLayers_( numberOfGhostLayers ) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) + { + typedef typename FieldType::value_type T; + const uint_t F_SIZE = FieldType::F_SIZE; + typedef GhostLayerField<T, F_SIZE> GlField_T; + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) + { + if ( numberOfGhostLayers_ > 0 ) + resultMPIDatatypeInfo_ = py::cast( make_shared< field::communication::UniformMPIDatatypeInfo<GlField_T> >( fieldId_, numberOfGhostLayers_ ) ); + else + resultMPIDatatypeInfo_ = py::cast( make_shared< field::communication::UniformMPIDatatypeInfo<GlField_T> >( fieldId_ ) ); + + } + } + py::object getResultUniformMPIDatatype() + { + return resultMPIDatatypeInfo_; + } + + private: + py::object resultMPIDatatypeInfo_; + shared_ptr< StructuredBlockStorage > blocks_; + BlockDataID fieldId_; + uint_t numberOfGhostLayers_; +}; + + +template<typename... FieldTypes> +static py::object UniformMPIDatatypeInfoWrapper(const shared_ptr<StructuredBlockForest> & blocks, + const std::string & name, uint_t numberOfGhostLayers ) +{ + BlockDataID fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + if ( blocks->begin() == blocks->end() ) { + // if no blocks are on this field an arbitrary PackInfo can be returned + return py::cast( make_shared< field::communication::UniformMPIDatatypeInfo<GhostLayerField<real_t,1>> >( fieldID, numberOfGhostLayers ) ); + } + + UniformMPIDatatypeInfoExporter exporter(blocks, fieldID, numberOfGhostLayers); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( std::ref(exporter) ); + if ( ! exporter.getResultUniformMPIDatatype() ) { + throw py::value_error("Failed to create UniformMPIDatatype"); + } + else { + return exporter.getResultUniformMPIDatatype(); + } +} + +//=================================================================================================================== +// +// exportStencilRestrictedPackInfo +// +//=================================================================================================================== + +template< typename T > +void exportStencilRestrictedPackInfo(py::module_& m) +{ + using field::communication::StencilRestrictedPackInfo; + { + typedef StencilRestrictedPackInfo< GhostLayerField< T, 9 >, stencil::D2Q9 > Pi; + py::class_< Pi, shared_ptr< Pi >, walberla::communication::UniformPackInfo >(m, "StencilRestrictedPackInfo_D2Q9"); + } + { + typedef StencilRestrictedPackInfo< GhostLayerField< T, 7 >, stencil::D3Q7 > Pi; + py::class_< Pi, shared_ptr< Pi >, walberla::communication::UniformPackInfo >(m, "StencilRestrictedPackInfo_D3Q7"); + } + { + typedef StencilRestrictedPackInfo< GhostLayerField< T, 15 >, stencil::D3Q15 > Pi; + py::class_< Pi, shared_ptr< Pi >, walberla::communication::UniformPackInfo >(m, + "StencilRestrictedPackInfo_D3Q15"); + } + { + typedef StencilRestrictedPackInfo< GhostLayerField< T, 19 >, stencil::D3Q19 > Pi; + py::class_< Pi, shared_ptr< Pi >, walberla::communication::UniformPackInfo >(m, + "StencilRestrictedPackInfo_D3Q19"); + } + { + typedef StencilRestrictedPackInfo< GhostLayerField< T, 27 >, stencil::D3Q27 > Pi; + py::class_< Pi, shared_ptr< Pi >, walberla::communication::UniformPackInfo >(m, + "StencilRestrictedPackInfo_D3Q27"); + } +} + +} // namespace internal + +namespace py = pybind11; +using namespace pybind11::literals; + +template< typename... FieldTypes > +void exportCommunicationClasses(py::module_& m) +{ + py::module_ m2 = m.def_submodule("field", "Field Extension of the waLBerla python bindings"); + internal::exportStencilRestrictedPackInfo< real_t >(m2); + + m2.def( + "createPackInfo", + [](const shared_ptr<StructuredBlockForest> & blocks, + const std::string & blockDataName, uint_t numberOfGhostLayers ) { + return internal::PackInfoWrapper< FieldTypes... >(blocks, blockDataName, numberOfGhostLayers); + }, + "blocks"_a, "blockDataName"_a, "numberOfGhostLayers"_a = uint_t(0)); + + m2.def( + "createMPIDatatypeInfo", + [](const shared_ptr<StructuredBlockForest> & blocks, + const std::string & blockDataName, uint_t numberOfGhostLayers ) { + return internal::UniformMPIDatatypeInfoWrapper< FieldTypes... >(blocks, blockDataName, numberOfGhostLayers); + }, + "blocks"_a, "blockDataName"_a, "numberOfGhostLayers"_a = uint_t(0)); +} + +} // namespace field +} // namespace walberla + +#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/python_coupling/export/FieldExport.impl.h b/src/python_coupling/export/FieldExport.impl.h new file mode 100644 index 000000000..ad9449849 --- /dev/null +++ b/src/python_coupling/export/FieldExport.impl.h @@ -0,0 +1,664 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file FieldExport.impl.h +//! \ingroup field +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== +#include "core/VectorTrait.h" +#include "core/logging/Logging.h" + +#include "field/AddToStorage.h" +#include "field/Field.h" +#include "field/FlagField.h" +#include "field/GhostLayerField.h" +#include "field/communication/PackInfo.h" +#include "field/communication/UniformMPIDatatypeInfo.h" +#include "field/vtk/FlagFieldMapping.h" +#include "field/vtk/VTKWriter.h" + +#include "python_coupling/PythonWrapper.h" +#include "python_coupling/helper/MplHelpers.h" +#include "python_coupling/helper/PybindHelper.h" + +#include <iostream> +#include <type_traits> + +#include "GatherExport.impl.h" +#include "pybind11/numpy.h" +#include <pybind11/stl.h> + +namespace walberla +{ +namespace field +{ + +//******************************************************************************************************************* +/*! Exports all Fields given in the Sequence +* +* Put only Fields in the sequence! The corresponding GhostLayerFields are exported automatically +* +* \warning Make sure that the same adaptor type is exported only once! +*/ +//******************************************************************************************************************* +template<typename... FieldTypes > +void exportFields(); + + + +//******************************************************************************************************************* +/*! Exports all GhostLayerFieldAdaptors given in the Sequence +* +* \warning Make sure that the same adaptor type is exported only once! +*/ +//******************************************************************************************************************* +template<typename... AdaptorTypes> +void exportGhostLayerFieldAdaptors(); + +template<typename AdaptorType> +void exportGhostLayerFieldAdaptor(); + +namespace internal +{ +namespace py = pybind11; + +template<class T> struct PythonFormatString { inline static char * get() { static char value [] = "B"; return value; } }; + +template<> struct PythonFormatString<double> { inline static char * get() { static char value [] = "d"; return value; } }; +template<> struct PythonFormatString<float> { inline static char * get() { static char value [] = "f"; return value; } }; +template<> struct PythonFormatString<unsigned short> { inline static char * get() { static char value [] = "H"; return value; } }; +template<> struct PythonFormatString<int> { inline static char * get() { static char value [] = "i"; return value; } }; +template<> struct PythonFormatString<unsigned int> { inline static char * get() { static char value [] = "I"; return value; } }; +template<> struct PythonFormatString<long> { inline static char * get() { static char value [] = "l"; return value; } }; +template<> struct PythonFormatString<unsigned long> { inline static char * get() { static char value [] = "L"; return value; } }; +template<> struct PythonFormatString<long long> { inline static char * get() { static char value [] = "q"; return value; } }; +template<> struct PythonFormatString<unsigned long long>{ inline static char * get() { static char value [] = "Q"; return value; } }; +template<> struct PythonFormatString<int8_t> { inline static char * get() { static char value [] = "c"; return value; } }; +template<> struct PythonFormatString<int16_t> { inline static char * get() { static char value [] = "h"; return value; } }; +template<> struct PythonFormatString<uint8_t> { inline static char * get() { static char value [] = "C"; return value; } }; + +//=================================================================================================================== +// +// Aligned Allocation +// +//=================================================================================================================== + +template< typename T > +shared_ptr< field::FieldAllocator< T > > getAllocator(uint_t alignment) +{ + if (alignment == 0) + return shared_ptr< field::FieldAllocator< T > >(); // leave to default - auto-detection of alignment + else if (alignment == 16) + return make_shared< field::AllocateAligned< T, 16 > >(); + else if (alignment == 32) + return make_shared< field::AllocateAligned< T, 32 > >(); + else if (alignment == 64) + return make_shared< field::AllocateAligned< T, 64 > >(); + else if (alignment == 128) + return make_shared< field::AllocateAligned< T, 128 > >(); + else + { + throw py::value_error("Alignment parameter has to be one of 0, 16, 32, 64, 128."); + return shared_ptr< field::FieldAllocator< T > >(); + } +} + +template< typename GhostLayerField_T > +class GhostLayerFieldDataHandling : public field::BlockDataHandling< GhostLayerField_T > +{ + public: + typedef typename GhostLayerField_T::value_type Value_T; + + GhostLayerFieldDataHandling(const weak_ptr< StructuredBlockStorage >& blocks, const uint_t nrOfGhostLayers, + const Value_T& initValue, const Layout layout, uint_t alignment = 0) + : blocks_(blocks), nrOfGhostLayers_(nrOfGhostLayers), initValue_(initValue), layout_(layout), + alignment_(alignment) + {} + + GhostLayerField_T* allocate(IBlock* const block) + { + auto blocks = blocks_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blocks, "Trying to access 'AlwaysInitializeBlockDataHandling' for a block " + "storage object that doesn't exist anymore"); + GhostLayerField_T* field = new GhostLayerField_T( + blocks->getNumberOfXCells(*block), blocks->getNumberOfYCells(*block), blocks->getNumberOfZCells(*block), + nrOfGhostLayers_, initValue_, layout_, getAllocator< Value_T >(alignment_)); + return field; + } + + GhostLayerField_T* reallocate(IBlock* const block) { return allocate(block); } + + private: + weak_ptr< StructuredBlockStorage > blocks_; + + uint_t nrOfGhostLayers_; + Value_T initValue_; + Layout layout_; + uint_t alignment_; +}; + +//=================================================================================================================== +// +// Field functions redefined for easier export +// +//=================================================================================================================== + +template< typename Field_T > +py::object field_size(const Field_T& field) +{ + return py::make_tuple(field.xSize(), field.ySize(), field.zSize(), field.fSize()); +} + +template< typename GlField_T > +py::tuple field_sizeWithGhostLayer(const GlField_T& field) +{ + return py::make_tuple(field.xSizeWithGhostLayer(), field.ySizeWithGhostLayer(), field.zSizeWithGhostLayer(), + field.fSize()); +} + +template< typename Field_T > +py::tuple field_allocSize(const Field_T& field) +{ + return py::make_tuple(field.xAllocSize(), field.yAllocSize(), field.zAllocSize(), field.fAllocSize()); +} + +template< typename Field_T > +py::tuple field_strides(const Field_T& field) +{ + return py::make_tuple(field.xStride(), field.yStride(), field.zStride(), field.fStride()); +} + +template< typename Field_T > +py::tuple field_offsets(const Field_T& field) +{ + return py::make_tuple(field.xOff(), field.yOff(), field.zOff()); +} + +template< typename Field_T > +py::object field_layout(const Field_T& f) +{ + return py::cast(f.layout()); +} + +template< typename Field_T > +void field_swapDataPointers(Field_T& f1, Field_T& f2) +{ + if (!f1.hasSameAllocSize(f2) || !f1.hasSameSize(f2) || f1.layout() != f2.layout()) + { + throw py::value_error("The data of fields with different sizes or layout cannot be swapped"); + } + f1.swapDataPointers(f2); +} + +template< typename Field_T > +py::object copyAdaptorToField(const Field_T& f) +{ + typedef GhostLayerField< typename Field_T::value_type, Field_T::F_SIZE > ResField; + auto res = make_shared< ResField >(f.xSize(), f.ySize(), f.zSize(), f.nrOfGhostLayers()); + + auto srcIt = f.beginWithGhostLayerXYZ(); + auto dstIt = res->beginWithGhostLayerXYZ(); + while (srcIt != f.end()) + { + for (cell_idx_t fCoord = 0; fCoord < cell_idx_c(Field_T::F_SIZE); ++fCoord) + dstIt.getF(fCoord) = srcIt.getF(fCoord); + + ++srcIt; + ++dstIt; + } + return py::cast(res); +} + +//=================================================================================================================== +// +// Field export +// +//=================================================================================================================== +template< typename Field_T > +py::array_t< typename Field_T::value_type > toNumpyArray(const Field_T& field) +{ + using T = typename Field_T::value_type; + const T* ptr = field.dataAt(0, 0, 0, 0); + + if (field.fSize() == 1) + { + return pybind11::array_t< T, 0 >({ field.xSize(), field.ySize(), field.zSize() }, + { static_cast< size_t >(field.xStride()) * sizeof(T), + static_cast< size_t >(field.yStride()) * sizeof(T), + static_cast< size_t >(field.zStride()) * sizeof(T) }, + ptr, py::cast(field)); + } + else + { + return pybind11::array_t< T, 0 >( + { field.xSize(), field.ySize(), field.zSize(), field.fSize() }, + { static_cast< size_t >(field.xStride()) * sizeof(T), static_cast< size_t >(field.yStride()) * sizeof(T), + static_cast< size_t >(field.zStride()) * sizeof(T), static_cast< size_t >(field.fStride()) * sizeof(T) }, + ptr, py::cast(field)); + } +} + +template< typename GlField_T > +py::array_t< typename GlField_T::value_type > toNumpyArrayWithGhostLayers(const GlField_T& field) +{ + using T = typename GlField_T::value_type; + const T* ptr = field.dataAt(-static_cast< cell_idx_t >(field.nrOfGhostLayers()), + -static_cast< cell_idx_t >(field.nrOfGhostLayers()), + -static_cast< cell_idx_t >(field.nrOfGhostLayers()), 0); + + + if (field.fSize() == 1) + { + return pybind11::array_t< T, 0 >({ field.xSizeWithGhostLayer(), field.ySizeWithGhostLayer(), field.zSizeWithGhostLayer() }, + { static_cast< size_t >(field.xStride()) * sizeof(T), + static_cast< size_t >(field.yStride()) * sizeof(T), + static_cast< size_t >(field.zStride()) * sizeof(T) }, + ptr, py::cast(field)); + } + else + { + return pybind11::array_t< T, 0 >( + { field.xSizeWithGhostLayer(), field.ySizeWithGhostLayer(), field.zSizeWithGhostLayer(), field.fSize() }, + { static_cast< size_t >(field.xStride()) * sizeof(T), static_cast< size_t >(field.yStride()) * sizeof(T), + static_cast< size_t >(field.zStride()) * sizeof(T), static_cast< size_t >(field.fStride()) * sizeof(T) }, + ptr, py::cast(field)); + } +} + + +struct FieldExporter +{ + FieldExporter(py::module_& m) : m_(m) {} + template< typename FieldType > + void operator()(python_coupling::NonCopyableWrap< FieldType >) const + { + typedef typename FieldType::value_type T; + const uint_t F_SIZE = FieldType::F_SIZE; + typedef GhostLayerField< T, F_SIZE > GlField_T; + typedef Field< T, F_SIZE > Field_T; + + std::string data_type_name = PythonFormatString<T>::get(); + + std::string class_name = "Field_" + data_type_name + "_" + std::to_string(FieldType::F_SIZE); + + py::class_< Field_T, shared_ptr< Field_T > >(m_, class_name.c_str()) + .def_property_readonly("layout", &field_layout< Field_T >) + .def_property_readonly("size", &field_size< Field_T >) + .def_property_readonly("allocSize", &field_allocSize< Field_T >) + .def_property_readonly("strides", &field_strides< Field_T >) + .def_property_readonly("offsets", &field_offsets< Field_T >) + .def("clone", &Field_T::clone, py::return_value_policy::copy) + .def("cloneUninitialized", &Field_T::cloneUninitialized, py::return_value_policy::copy) + .def("swapDataPointers", &field_swapDataPointers< Field_T >) + .def("__getitem__", [](const Field_T& self, const py::object& index) { + return py::cast(self).attr("__array__")().attr("__getitem__")(index); + } ) + .def("__setitem__", [](const Field_T& self, const py::object& index, + const typename Field_T::value_type& value) { + py::cast(self).attr("__array__")().attr("__setitem__")(index, value); + } ) + .def("__array__", &toNumpyArray< Field_T >); + + std::string class_nameGL = + "GhostLayerField_" + data_type_name + "_" + std::to_string(FieldType::F_SIZE); + + py::class_< GlField_T, shared_ptr< GlField_T >, Field_T >(m_, class_nameGL.c_str()) + .def_property_readonly("sizeWithGhostLayer", &GlField_T::xSizeWithGhostLayer) + .def_property_readonly("nrOfGhostLayers", &GlField_T::nrOfGhostLayers) + .def("__array__", &toNumpyArrayWithGhostLayers< GlField_T >); + + using field::communication::PackInfo; + std::string FieldPackInfo_name = "FieldPackInfo_" + data_type_name + "_" + std::to_string(FieldType::F_SIZE); + py::class_< PackInfo< GlField_T >, shared_ptr< PackInfo< GlField_T > >, walberla::communication::UniformPackInfo >(m_, FieldPackInfo_name.c_str()); + + using field::communication::UniformMPIDatatypeInfo; + std::string FieldMPIDataTypeInfo_name = "FieldMPIDataTypeInfo_" + data_type_name + "_" + std::to_string(FieldType::F_SIZE); + py::class_< UniformMPIDatatypeInfo< GlField_T >, shared_ptr< UniformMPIDatatypeInfo< GlField_T > >, walberla::communication::UniformMPIDatatypeInfo >( + m_, FieldMPIDataTypeInfo_name.c_str()); + } + const py::module_& m_; +}; + + +struct FieldAllocatorExporter +{ + FieldAllocatorExporter(py::module_& m) : m_(m) {} + template< typename T > + void operator()(python_coupling::NonCopyableWrap< T >) const + { + std::string data_type_name = PythonFormatString<T>::get(); + std::string class_nameFieldAllocator = "FieldAllocator_" + data_type_name; + py::class_< FieldAllocator< T >, shared_ptr< FieldAllocator< T > > >(m_, class_nameFieldAllocator.c_str()) + .def("incrementReferenceCount", &FieldAllocator< T >::incrementReferenceCount) + .def("decrementReferenceCount", &FieldAllocator< T >::decrementReferenceCount); + } + const py::module_& m_; +}; + +//=================================================================================================================== +// +// addToStorage +// +//=================================================================================================================== + +class AddToStorageExporter +{ + public: + AddToStorageExporter(const shared_ptr< StructuredBlockForest >& blocks, const std::string& name, py::object& dtype, uint_t fs, + uint_t gl, Layout layout, real_t initValue, uint_t alignment) + : blocks_(blocks), name_(name), dtype_(dtype), fs_(fs), gl_(gl), layout_(layout), initValue_(initValue), alignment_(alignment), found_(false) + {} + + template< typename FieldType > + void operator()(python_coupling::NonCopyableWrap<FieldType>) const + { + using namespace py; + typedef typename FieldType::value_type T; + const uint_t F_SIZE = FieldType::F_SIZE; + + if (F_SIZE != fs_) return; + if(python_coupling::isCppEqualToPythonType<T>(py::cast<std::string>(dtype_.attr("__name__")))) + { + typedef internal::GhostLayerFieldDataHandling< GhostLayerField< T, F_SIZE > > DataHandling; + auto dataHandling = walberla::make_shared< DataHandling >(blocks_, gl_, initValue_, layout_, alignment_); + blocks_->addBlockData(dataHandling, name_); + } + found_ = true; + } + + bool successful() const { return found_; } + + private: + shared_ptr< StructuredBlockStorage > blocks_; + std::string name_; + py::object dtype_; + uint_t fs_; + uint_t gl_; + Layout layout_; + real_t initValue_; + uint_t alignment_; + mutable bool found_; +}; + +template< typename... FieldTypes > +void addToStorage(const shared_ptr< StructuredBlockForest >& blocks, const std::string& name, py::object& dtype, + uint_t fs, uint_t gl, Layout layout, real_t initValue, uint_t alignment) +{ + using namespace py; + + auto result = make_shared< py::object >(); + AddToStorageExporter exporter(blocks, name, dtype, fs, gl, layout, initValue, alignment); + python_coupling::for_each_noncopyable_type< FieldTypes... >(exporter); + + if (!exporter.successful()) + { + throw py::value_error("Adding GhostLayerField failed. Maybe the data type and/or the fsize is not exported to python yet"); + } +} + +inline void addFlagFieldToStorage(const shared_ptr< StructuredBlockStorage >& blocks, const std::string& name, + uint_t nrOfBits, uint_t gl) +{ + if (nrOfBits == 8) + field::addFlagFieldToStorage< FlagField< uint8_t > >(blocks, name, gl); + else if (nrOfBits == 16) + field::addFlagFieldToStorage< FlagField< uint16_t > >(blocks, name, gl); + else if (nrOfBits == 32) + field::addFlagFieldToStorage< FlagField< uint32_t > >(blocks, name, gl); + else if (nrOfBits == 64) + field::addFlagFieldToStorage< FlagField< uint64_t > >(blocks, name, gl); + else + { + throw py::value_error("Allowed values for number of bits are: 8,16,32,64"); + } +} + +//=================================================================================================================== +// +// createField +// +//=================================================================================================================== + +class CreateFieldExporter +{ + public: + CreateFieldExporter( uint_t xs, uint_t ys, uint_t zs, uint_t fs, uint_t gl, + Layout layout, const py::object & dtype, uint_t alignment, + const shared_ptr<py::object> & resultPointer ) + : xs_( xs ), ys_(ys), zs_(zs), fs_(fs), gl_(gl), + layout_( layout), dtype_( dtype ), alignment_(alignment), resultPointer_( resultPointer ) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) const + { + typedef typename FieldType::value_type T; + const uint_t F_SIZE = FieldType::F_SIZE; + + if( F_SIZE != fs_ ) + return; + + if(python_coupling::isCppEqualToPythonType<T>(py::cast<std::string>(dtype_.attr("__name__")))) + { + T initVal = T(); + *resultPointer_ = py::cast( make_shared< GhostLayerField<T, F_SIZE> >( xs_,ys_,zs_, gl_, initVal, layout_, + getAllocator<T>(alignment_))); + } + } + + private: + uint_t xs_; + uint_t ys_; + uint_t zs_; + uint_t fs_; + uint_t gl_; + Layout layout_; + py::object dtype_; + uint_t alignment_; + shared_ptr<py::object> resultPointer_; +}; + +template<typename... FieldTypes> +py::object createPythonField( std::array< uint_t, 4 > size, + py::object & dtype, + uint_t ghostLayers, + Layout layout, + uint_t alignment) +{ + uint_t xSize = size[0]; + uint_t ySize = size[1]; + uint_t zSize = size[2]; + uint_t fSize = size[3]; + + auto result = make_shared<py::none>(); + CreateFieldExporter exporter( xSize,ySize, zSize, fSize, ghostLayers, layout, dtype, alignment, result ); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( exporter ); + + return *result; +} + +//=================================================================================================================== +// +// createVTKWriter +// +//=================================================================================================================== + +class CreateVTKWriterExporter +{ + public: + CreateVTKWriterExporter( const shared_ptr<StructuredBlockForest> & blocks, + ConstBlockDataID fieldId, const std::string & vtkName) + : blocks_( blocks ), fieldId_(fieldId), vtkName_( vtkName ) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) + { + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) + writer_ = shared_ptr<field::VTKWriter<FieldType> >( new field::VTKWriter<FieldType>(fieldId_, vtkName_)); + } + + shared_ptr< vtk::BlockCellDataWriterInterface > getCreatedWriter() { + return writer_; + } + + private: + shared_ptr< vtk::BlockCellDataWriterInterface > writer_; + shared_ptr< StructuredBlockStorage > blocks_; + ConstBlockDataID fieldId_; + std::string vtkName_; +}; + + +template<typename... FieldTypes> +inline shared_ptr<vtk::BlockCellDataWriterInterface> createVTKWriter(const shared_ptr<StructuredBlockForest> & blocks, + const std::string & name, + const std::string & nameInVtkOutput = "") +{ + std::string vtkName = nameInVtkOutput; + if( vtkName.empty()) + vtkName = name; + + if ( blocks->begin() == blocks->end() ) + return shared_ptr<vtk::BlockCellDataWriterInterface>(); + auto fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + CreateVTKWriterExporter exporter(blocks, fieldID, vtkName); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( std::ref(exporter) ); + if ( ! exporter.getCreatedWriter() ) { + throw py::value_error("Failed to create VTK writer"); + } + else { + return exporter.getCreatedWriter(); + } +} + + +//=================================================================================================================== +// +// createBinarizationFieldWriter +// +//=================================================================================================================== + +class CreateBinarizationVTKWriterExporter +{ + public: + CreateBinarizationVTKWriterExporter( const shared_ptr<StructuredBlockStorage> & blocks, + ConstBlockDataID fieldId, const std::string & vtkName, uint_t mask) + : blocks_( blocks ), fieldId_(fieldId), vtkName_( vtkName ), mask_(mask) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) + { + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) + { + typedef field::BinarizationFieldWriter< FieldType > Writer; + writer_ = shared_ptr< Writer >(new Writer(fieldId_, vtkName_, static_cast< typename FieldType::value_type >(mask_))); + } + } + + shared_ptr< vtk::BlockCellDataWriterInterface > getCreatedWriter() { + return writer_; + } + + private: + shared_ptr< vtk::BlockCellDataWriterInterface > writer_; + shared_ptr< StructuredBlockStorage > blocks_; + ConstBlockDataID fieldId_; + std::string vtkName_; + uint_t mask_; +}; + + +template<typename... FieldTypes> +inline shared_ptr<vtk::BlockCellDataWriterInterface> createBinarizationVTKWriter(const shared_ptr<StructuredBlockStorage> & blocks, + const std::string & name, + uint_t mask, + const std::string & nameInVtkOutput = "") +{ + std::string vtkName = nameInVtkOutput; + if( vtkName.empty()) + vtkName = name; + + if ( blocks->begin() == blocks->end() ) + return shared_ptr<vtk::BlockCellDataWriterInterface>(); + auto fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + + CreateBinarizationVTKWriterExporter exporter(blocks, fieldID, vtkName, mask); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( std::ref(exporter) ); + if ( ! exporter.getCreatedWriter() ) { + throw py::value_error("Failed to create binarization field writer"); + } + else { + return exporter.getCreatedWriter(); + } +} + +} // namespace internal + +namespace py = pybind11; +template< typename... FieldTypes > +void exportFields(py::module_& m) +{ + using namespace py; + + py::module_ m2 = m.def_submodule("field", "Field Extension of the waLBerla python bindings"); + + py::enum_< Layout >(m2, "Layout").value("fzyx", fzyx).value("zyxf", zyxf).export_values(); + + python_coupling::for_each_noncopyable_type< FieldTypes... >(internal::FieldExporter(m2)); + python_coupling::for_each_noncopyable_type< real_t, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t >(internal::FieldAllocatorExporter(m2)); + + m2.def( + "createField", + [](std::array< uint_t, 4 > size, py::object & dtype, uint_t ghostLayers, Layout layout, uint_t alignment) { + return internal::createPythonField< FieldTypes... >(size, dtype, ghostLayers, layout, alignment); + }, + "size"_a, "dtype"_a, "ghostLayers"_a = uint_t(1), "layout"_a = zyxf, "alignment"_a = 0); + + m2.def( + "addToStorage", + [](const shared_ptr< StructuredBlockForest > & blocks, const std::string & name, py::object &dtype, uint_t fSize, + Layout layout, uint_t ghostLayers, real_t initValue, uint_t alignment) { + return internal::addToStorage< FieldTypes... >(blocks, name, dtype, fSize, ghostLayers, layout, initValue, alignment); + }, + "blocks"_a, "name"_a, "dtype"_a, "fSize"_a = 1, "layout"_a = zyxf, "ghostLayers"_a = uint_t(1), "initValue"_a = 0.0, "alignment"_a = 0); + + m2.def( "createVTKWriter", + [](const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, + const std::string & nameInVtkOutput = ""){ + return internal::createVTKWriter< FieldTypes... >(blocks, name, nameInVtkOutput); + }, + "blocks"_a, "name"_a, "nameInVtkOutput"_a="" ); + + + #define UintFields Field<uint8_t,1 >, Field<uint16_t, 1>, Field<uint32_t, 1>, Field<uint64_t, 1> + m2.def( "createBinarizationVTKWriter", + [](const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, + uint_t mask, const std::string & nameInVtkOutput = ""){ + return internal::createBinarizationVTKWriter< UintFields >(blocks, name, mask, nameInVtkOutput); + }, + "blocks"_a, "name"_a, "mask"_a, "nameInVtkOutput"_a="" ); + + +} + +} // namespace field +} // namespace walberla diff --git a/src/field/python/Exports.h b/src/python_coupling/export/FieldExports.h similarity index 63% rename from src/field/python/Exports.h rename to src/python_coupling/export/FieldExports.h index c07bad868..385ad4fe7 100644 --- a/src/field/python/Exports.h +++ b/src/python_coupling/export/FieldExports.h @@ -13,9 +13,10 @@ // You should have received a copy of the GNU General Public License along // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // -//! \file Exports.h +//! \file FieldExports.h //! \ingroup field //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -26,10 +27,10 @@ #ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/helper/ModuleScope.h" -#include "FieldExport.h" -#include "GatherExport.h" -#include "CommunicationExport.h" +# include "python_coupling/PythonWrapper.h" + +# include "FieldCommunicationExport.h" +# include "FieldExport.impl.h" namespace walberla { namespace field { @@ -38,26 +39,17 @@ namespace field { //******************************************************************************************************************* /*! Exports the field types and corresponding function given in the type sequence to python * - * Automatically exports the Field, GhostLayerField and (if possible) the FlagField version, - * so it is enough to add the basic Field<> types to the sequence - * For fields that store and unsigned integer type and have an fSize of one, the FlagField is also exported + * Automatically exports the Field and GhostLayerField * - * Example: - \code - typedef boost::mpl::vector< Field<real_t,1>, - Field<uint16_t, 1> - Field<Vector3<real_t>, 1 > FieldVector; - \endcode - - * This exports the following types: + * For example, with the template arguments Field<real_t,1> and Field<uint16_t, 1>, + * this exports the following types: * - Field<real_t,1>, GhostLayerField<real_t,1> - * - Field<uint16_t,1>, GhostLayerField<uint16_t,1>, FlagField<uint16_t> - * - Field< Vector3<real_t>, 1 > , GhostLayerField< Vector3<real_t>, 1 > + * - Field<uint16_t,1>, GhostLayerField<uint16_t,1> * * Additionally the following free functions are exported - * - field.createField - * - field.createFlagField * - field.addToStorage + * - field.createVTKWriter + * - field.createBinarizationVTKWriter * - field.createPackInfo * - field.createMPIDatatypeInfo * @@ -66,14 +58,11 @@ namespace field { * \warning Make sure that the same field type is exported only once! */ //******************************************************************************************************************* - template<typename FieldTypes> - void exportModuleToPython() + template<typename... FieldTypes> + void exportModuleToPython(py::module_ &m) { - python_coupling::ModuleScope fieldModule( "field" ); - - exportFields<FieldTypes>(); - - exportCommunicationClasses<FieldTypes>(); + exportFields<FieldTypes...>(m); + exportCommunicationClasses<FieldTypes...>(m); } diff --git a/src/python_coupling/export/GatherExport.impl.h b/src/python_coupling/export/GatherExport.impl.h new file mode 100644 index 000000000..6d567f075 --- /dev/null +++ b/src/python_coupling/export/GatherExport.impl.h @@ -0,0 +1,141 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file GatherExport.impl.h +//! \ingroup field +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#include "field/Gather.h" +#include "python_coupling/helper/MplHelpers.h" +#include "python_coupling/helper/BlockStorageExportHelpers.h" +#include "python_coupling/helper/SliceToCellInterval.h" + + +namespace walberla { +namespace field { + +//******************************************************************************************************************* +/*! Exports the gather functionality of waLberla +* +* With field.gather a corresponding field will the gathered to the specified process. This field can be viewed as a +* numpy array with field.toArrayOn all other porcesses an empty pybind11::object will be returned. +* +* \hint For large scale simulations it is also possible to provide a slice to keep the gathered data low! +*/ +//******************************************************************************************************************* +namespace py = pybind11; +template<typename... FieldTypes > +void exportGatherFunctions(py::module_ &m); + +namespace internal { +namespace py = pybind11; + //=================================================================================================================== + // + // Gather + // + //=================================================================================================================== + +class GatherExporter +{ + public: + GatherExporter(const shared_ptr<StructuredBlockForest> & blocks, ConstBlockDataID fieldId, + CellInterval boundingBox = CellInterval(), int targetRank = 0 ) + : blocks_( blocks ), fieldId_(fieldId), boundingBox_( boundingBox ), targetRank_(targetRank) + {} + + template< typename FieldType> + void operator() ( python_coupling::NonCopyableWrap<FieldType> ) + { + typedef Field< typename FieldType::value_type, FieldType::F_SIZE > ResultField; + IBlock * firstBlock = & ( * blocks_->begin() ); + if( firstBlock->isDataClassOrSubclassOf<FieldType>(fieldId_) ) + { + auto result = make_shared< ResultField > ( 0,0,0 ); + field::gather< FieldType, ResultField > ( *result, blocks_, fieldId_, boundingBox_, targetRank_, MPI_COMM_WORLD ); + + if ( MPIManager::instance()->worldRank() == targetRank_ ) + resultField_ = py::cast(result); + else + resultField_ = py::none(); + + } + } + py::object getResultField() + { + return resultField_; + } + + private: + py::object resultField_; + shared_ptr< StructuredBlockStorage > blocks_; + ConstBlockDataID fieldId_; + std::string vtkName_; + CellInterval boundingBox_; + int targetRank_ ; +}; + + +template<typename... FieldTypes> +static py::object gatherWrapper(const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, + const py::tuple & slice, int targetRank = 0 ) +{ + BlockDataID fieldID = python_coupling::blockDataIDFromString( *blocks, name ); + CellInterval boundingBox = python_coupling::globalPythonSliceToCellInterval( blocks, slice ); + + if ( blocks->begin() == blocks->end() ) { + // if no blocks are on this process the field::gather function can be called with any type + // however we have to call it, otherwise a deadlock occurs + auto result = make_shared< Field<real_t, 1> > ( 0,0,0 ); + field::gather< Field<real_t, 1>, Field<real_t, 1> > ( *result, blocks, fieldID, boundingBox, targetRank, MPI_COMM_WORLD ); + return py::none(); + } + + GatherExporter exporter(blocks, fieldID, boundingBox, targetRank); + python_coupling::for_each_noncopyable_type< FieldTypes... > ( std::ref(exporter) ); + + if ( ! exporter.getResultField() ) { + throw py::value_error("Failed to gather Field"); + } + else { + return exporter.getResultField(); + } +} + +} // namespace internal + + +namespace py = pybind11; +using namespace pybind11::literals; +template<typename... FieldTypes > +void exportGatherFunctions(py::module_ &m) +{ + py::module_ m2 = m.def_submodule("field", "Field Extension of the waLBerla python bindings"); + + m2.def( + "gather", + [](const shared_ptr<StructuredBlockForest> & blocks, const std::string & name, + const py::tuple & slice, int targetRank = 0 ) { + return internal::gatherWrapper< FieldTypes... >(blocks, name, slice, targetRank); + }, + "blocks"_a, "name"_a, "slice"_a, "targetRank"_a = uint_t(0)); +} + +} // namespace moduleName +} // namespace walberla + + diff --git a/src/python_coupling/export/MPIExport.cpp b/src/python_coupling/export/MPIExport.cpp new file mode 100644 index 000000000..00e13dfdf --- /dev/null +++ b/src/python_coupling/export/MPIExport.cpp @@ -0,0 +1,278 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file MPIExports.cpp +//! \ingroup python_coupling +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#include "python_coupling/PythonWrapper.h" + +#ifdef WALBERLA_BUILD_WITH_PYTHON + +#include "python_coupling/helper/PythonIterableToStdVector.h" + +#include "core/mpi/MPIManager.h" +#include "core/mpi/Reduce.h" +#include "core/mpi/Gather.h" +#include "core/mpi/Broadcast.h" + +#include <vector> +#include "pybind11/stl.h" + +namespace py = pybind11; + + + + +namespace walberla { +namespace python_coupling { + + typedef std::vector<int64_t> IntStdVector; + typedef std::vector<real_t> RealStdVector; + typedef std::vector<std::string> StringStdVector; + + + //=================================================================================================================== + // + // MPIManager + // + //=================================================================================================================== + + + static int rank() { return MPIManager::instance()->rank(); } + static int worldRank() { return MPIManager::instance()->worldRank(); } + static int numProcesses() { return MPIManager::instance()->numProcesses(); } + static bool hasCartesianSetup() { return MPIManager::instance()->hasCartesianSetup(); } + static bool rankValid() { return MPIManager::instance()->rankValid(); } + + + + //=================================================================================================================== + // + // Broadcast + // + //=================================================================================================================== + + static py::object broadcast_string( py::object value, int sendRank ) //NOLINT + { + if ( py::isinstance<std::string>(value) ) + { + std::string extractedValue = py::cast< std::string >(value); + mpi::broadcastObject( extractedValue , sendRank ); + return py::cast( extractedValue ); + } + StringStdVector extractedValue = pythonIterableToStdVector< StringStdVector::value_type >( value ); + mpi::broadcastObject( extractedValue, sendRank ); + return py::cast( extractedValue ); + } + + static py::object broadcast_int( py::object value, int sendRank ) //NOLINT + { + if ( py::isinstance<int64_t>(value) ) + { + int64_t extractedValue = py::cast< int64_t >(value); + mpi::broadcastObject( extractedValue , sendRank ); + return py::cast( extractedValue ); + } + IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); + mpi::broadcastObject( extractedValue, sendRank ); + return py::cast( extractedValue ); + } + + static py::object broadcast_real( py::object value, int sendRank ) //NOLINT + { + if ( py::isinstance<real_t>(value) ) + { + real_t extractedValue = py::cast< real_t >(value); + mpi::broadcastObject( extractedValue , sendRank); + return py::cast( extractedValue ); + } + RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); + mpi::broadcastObject( extractedValue , sendRank); + return py::cast( extractedValue ); + } + + + //=================================================================================================================== + // + // Reduce + // + //=================================================================================================================== + + + static py::object reduce_int( py::object value, mpi::Operation op, int recvRank ) //NOLINT + { + if ( py::isinstance<int64_t>(value) ) + { + int64_t extractedValue = py::cast< int64_t >(value); + mpi::reduceInplace( extractedValue , op, recvRank ); + return py::cast( extractedValue ); + } + IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); + mpi::reduceInplace( extractedValue, op, recvRank ); + return py::cast( extractedValue ); + } + + static py::object reduce_real( py::object value, mpi::Operation op, int recvRank ) //NOLINT + { + if ( py::isinstance<real_t>(value) ) + { + real_t extractedValue = py::cast< real_t >(value); + mpi::reduceInplace( extractedValue , op, recvRank); + return py::cast( extractedValue ); + } + RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); + mpi::reduceInplace( extractedValue , op, recvRank); + return py::cast( extractedValue ); + } + + + static py::object allreduce_int( py::object value, mpi::Operation op ) //NOLINT + { + if ( py::isinstance<int64_t>(value) ) + { + int64_t extractedValue = py::cast< int64_t >(value); + mpi::allReduceInplace( extractedValue , op ); + return py::cast( extractedValue ); + } + IntStdVector extractedValue = pythonIterableToStdVector< IntStdVector::value_type >( value ); + mpi::allReduceInplace( extractedValue, op ); + return py::cast( extractedValue ); + } + + static py::object allreduce_real( py::object value, mpi::Operation op ) //NOLINT + { + if ( py::isinstance<real_t>(value) ) + { + real_t extractedValue = py::cast< real_t >(value); + mpi::allReduceInplace( extractedValue , op ); + return py::cast( extractedValue ); + } + RealStdVector extractedValue = pythonIterableToStdVector< RealStdVector::value_type >( value ); + mpi::allReduceInplace( extractedValue , op ); + return py::cast( extractedValue ); + } + + + //=================================================================================================================== + // + // Gather + // + //=================================================================================================================== + + static IntStdVector gather_int( py::object value, int recvRank ) //NOLINT + { + if ( ! py::isinstance<int64_t>(value) ) + { + throw py::cast_error("Could not gather the given value - unknown type"); + } + int64_t extractedValue = py::cast< int64_t >(value); + return mpi::gather( extractedValue , recvRank ); + } + + static RealStdVector gather_real( py::object value, int recvRank ) //NOLINT + { + if ( ! py::isinstance<real_t>(value) ) + { + throw py::cast_error("Could not gather the given value - unknown type"); + } + real_t extractedValue = py::cast< real_t >(value); + return mpi::gather( extractedValue , recvRank); + } + + + static IntStdVector allgather_int( py::object value ) //NOLINT + { + if ( ! py::isinstance<int64_t>(value) ) + { + throw py::cast_error("Could not gather the given value - unknown type"); + } + int64_t extractedValue = py::cast< int64_t >(value); + return mpi::allGather( extractedValue ); + } + + static RealStdVector allgather_real( py::object value ) //NOLINT + { + if ( ! py::isinstance<real_t>(value) ) + { + throw py::cast_error("Could not gather the given value - unknown type"); + } + real_t extractedValue = py::cast< real_t >(value); + return mpi::allGather( extractedValue ); + } + + + + //=================================================================================================================== + // + // Export + // + //=================================================================================================================== + + static void worldBarrier() + { + WALBERLA_MPI_WORLD_BARRIER(); + } + + + void exportMPI(py::module_ &m) + { + py::module_ m2 = m.def_submodule("mpi", "MPI Extension of the waLBerla python bindings"); + + m2.def( "rank" , &rank ); + m2.def( "worldRank" , &worldRank ); + m2.def( "numProcesses" , &numProcesses ); + m2.def( "hasCartesianSetup", &hasCartesianSetup); + m2.def( "rankValid" , &rankValid ); + m2.def( "worldBarrier" , &worldBarrier ); + + py::enum_<mpi::Operation>(m2, "Operation") + .value("MIN" , mpi::MIN ) + .value("MAX" , mpi::MAX ) + .value("SUM" , mpi::SUM ) + .value("PRODUCT", mpi::PRODUCT ) + .value("LOGICAL_AND", mpi::LOGICAL_AND ) + .value("BITWISE_AND", mpi::BITWISE_AND ) + .value("LOGICAL_OR", mpi::LOGICAL_OR ) + .value("BITWISE_OR", mpi::BITWISE_OR ) + .value("LOGICAL_XOR", mpi::LOGICAL_XOR ) + .value("BITWISE_XOR", mpi::BITWISE_XOR ) + .export_values(); + + m2.def( "broadcastInt", &broadcast_int); + m2.def( "broadcastReal", &broadcast_real); + m2.def( "broadcastString",&broadcast_string); + + m2.def( "reduceInt", &reduce_int); + m2.def( "reduceReal", &reduce_real); + m2.def( "allreduceInt", &allreduce_int ); + m2.def( "allreduceReal", &allreduce_real ); + + m2.def( "gatherInt", &gather_int); + m2.def( "gatherReal", &gather_real); + m2.def( "allgatherInt", &allgather_int ); + m2.def( "allgatherReal", &allgather_real ); + } + + + +} // namespace python_coupling +} // namespace walberla + + +#endif diff --git a/src/python_coupling/basic_exports/MPIExport.h b/src/python_coupling/export/MPIExport.h similarity index 93% rename from src/python_coupling/basic_exports/MPIExport.h rename to src/python_coupling/export/MPIExport.h index 8e15eb1e7..d1653a734 100644 --- a/src/python_coupling/basic_exports/MPIExport.h +++ b/src/python_coupling/export/MPIExport.h @@ -16,6 +16,7 @@ //! \file MPIExports.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -27,7 +28,7 @@ namespace python_coupling { - void exportMPI(); + void exportMPI(py::module_ &m); diff --git a/src/vtk/python/Exports.cpp b/src/python_coupling/export/VTKExport.cpp similarity index 53% rename from src/vtk/python/Exports.cpp rename to src/python_coupling/export/VTKExport.cpp index 719a6baeb..a8ab00e57 100644 --- a/src/vtk/python/Exports.cpp +++ b/src/python_coupling/export/VTKExport.cpp @@ -14,7 +14,7 @@ // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // //! \file Exports.cpp -//! \ingroup timeloop +//! \ingroup vtk //! \author Martin Bauer <martin.bauer@fau.de> // //====================================================================================================================== @@ -24,68 +24,59 @@ #ifdef WALBERLA_BUILD_WITH_PYTHON -#include "python_coupling/Manager.h" -#include "python_coupling/helper/ModuleScope.h" - +#include "blockforest/StructuredBlockForest.h" #include "vtk/VTKOutput.h" -using namespace boost::python; - - namespace walberla { namespace vtk { namespace internal { +namespace py = pybind11; - shared_ptr<VTKOutput> VTKOutput_create(const shared_ptr<StructuredBlockStorage> & sbs, const std::string & identifier, - const std::string & baseFolder, const std::string & executionFolder, - const bool binary, const bool littleEndian, const bool useMPIIO, - uint_t ghostLayers=0) - { - return createVTKOutput_BlockData(*sbs, identifier, 1, ghostLayers, false, baseFolder, executionFolder, - true, binary, littleEndian, useMPIIO, 0); - } - - void VTKOutput_write(const shared_ptr<VTKOutput> &vtkOut, int step) - { - if (step < 0) - { - PyErr_SetString(PyExc_ValueError, "Step parameter has to be positive"); - throw boost::python::error_already_set(); - } - vtkOut->forceWrite(uint_c(step)); - } +void VTKOutput_write(const shared_ptr<VTKOutput> &vtkOut, int step) +{ + if (step < 0) + { + throw py::value_error("Step parameter has to be positive"); + } + vtkOut->forceWrite(uint_c(step)); +} } // namespace internal - -void exportModuleToPython() +namespace py = pybind11; +using namespace pybind11::literals; +void exportModuleToPython(py::module_& m) { - python_coupling::ModuleScope timeloopModule( "vtk" ); + py::module_ m2 = m.def_submodule("vtk", "VTK Extension of the waLBerla python bindings"); void ( VTKOutput::*p_setSamplingResolution1) ( const real_t ) = &VTKOutput::setSamplingResolution; void ( VTKOutput::*p_setSamplingResolution2) ( const real_t, const real_t, const real_t ) = &VTKOutput::setSamplingResolution; - class_<BlockCellDataWriterInterface, //NOLINT - boost::noncopyable, - shared_ptr<BlockCellDataWriterInterface> > ("BlockCellDataWriterInterface", no_init) - ; + py::class_<BlockCellDataWriterInterface, shared_ptr<BlockCellDataWriterInterface> > (m2, "BlockCellDataWriterInterface"); + + m2.def( + "makeOutput", + [](const shared_ptr<StructuredBlockForest> & sbf, const std::string & identifier, + const std::string & baseFolder, const std::string & executionFolder, + const bool binary, const bool littleEndian, const bool useMPIIO, uint_t ghostLayers=0) + { + return createVTKOutput_BlockData(*sbf, identifier, 1, ghostLayers, false, baseFolder, executionFolder, + true, binary, littleEndian, useMPIIO, 0); + }, + "blocks"_a, "name"_a, "baseFolder"_a=".", "executionFolder"_a="vtk", "binary"_a=true, + "littleEndian"_a=true, "useMPIIO"_a=true, "ghostLayers"_a=0); - def("makeOutput", internal::VTKOutput_create, (arg("blocks"), arg("name"), arg("baseFolder")=".", - arg("executionFolder")="vtk", arg("binary")=true, - arg("littleEndian")=true, arg("useMPIIO")=true, - arg("ghostLayers")=0)); - class_<VTKOutput, shared_ptr<VTKOutput>, boost::noncopyable > ("VTKOutput", no_init) + py::class_<VTKOutput, shared_ptr<VTKOutput> > (m2, "VTKOutput") .def( "addCellDataWriter" , &VTKOutput::addCellDataWriter ) .def( "write" , &internal::VTKOutput_write ) .def( "__call__" , &internal::VTKOutput_write ) .def( "addAABBInclusionFilter", &VTKOutput::addAABBInclusionFilter ) .def( "addAABBExclusionFilter", &VTKOutput::addAABBExclusionFilter ) .def( "setSamplingResolution" , p_setSamplingResolution1 ) - .def( "setSamplingResolution" , p_setSamplingResolution2 ) - ; + .def( "setSamplingResolution" , p_setSamplingResolution2 ); } diff --git a/src/vtk/python/Exports.h b/src/python_coupling/export/VTKExport.h similarity index 65% rename from src/vtk/python/Exports.h rename to src/python_coupling/export/VTKExport.h index a14e864cd..d2f955cd7 100644 --- a/src/vtk/python/Exports.h +++ b/src/python_coupling/export/VTKExport.h @@ -16,6 +16,7 @@ //! \file Exports.h //! \ingroup vtk //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -25,11 +26,21 @@ #ifdef WALBERLA_BUILD_WITH_PYTHON +#include <pybind11/pybind11.h> namespace walberla { namespace vtk { +namespace py = pybind11; - void exportModuleToPython(); + //******************************************************************************************************************* + /*! Exports the vtk functionality of waLBerla + * + * With vtk.makeOutput a instance of VTKOutput will be provided for python. I can be used together with + * field.createVTKWriter and field.createBinarizationVTKWriter to get the VTK output + */ + //******************************************************************************************************************* + + void exportModuleToPython(py::module_ &m); } // namespace vtk } // namespace walberla diff --git a/src/python_coupling/helper/BlockStorageExportHelpers.cpp b/src/python_coupling/helper/BlockStorageExportHelpers.cpp index 4555f0e7d..016416d11 100644 --- a/src/python_coupling/helper/BlockStorageExportHelpers.cpp +++ b/src/python_coupling/helper/BlockStorageExportHelpers.cpp @@ -16,6 +16,7 @@ //! \file BlockStorageExportHelpers.cpp //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -25,24 +26,22 @@ namespace walberla { namespace python_coupling { - +namespace py = pybind11; #ifdef WALBERLA_BUILD_WITH_PYTHON void NoSuchBlockData::translate( const NoSuchBlockData & e ) { - PyErr_SetString(PyExc_RuntimeError, e.what() ); + throw py::cast_error(e.what()); } void BlockDataNotConvertible::translate( const BlockDataNotConvertible & e ) { - PyErr_SetString(PyExc_RuntimeError, e.what() ); + throw py::cast_error(e.what()); } #else -void NoSuchBlockData::translate( const NoSuchBlockData & ) { -} +void NoSuchBlockData::translate( const NoSuchBlockData & ) {} -void BlockDataNotConvertible::translate( const BlockDataNotConvertible & ) { -} +void BlockDataNotConvertible::translate( const BlockDataNotConvertible & ) {} #endif diff --git a/src/python_coupling/helper/BlockStorageExportHelpers.h b/src/python_coupling/helper/BlockStorageExportHelpers.h index 227b45474..0b41dd498 100644 --- a/src/python_coupling/helper/BlockStorageExportHelpers.h +++ b/src/python_coupling/helper/BlockStorageExportHelpers.h @@ -16,6 +16,7 @@ //! \file ExportHelpers.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -56,16 +57,6 @@ namespace python_coupling { #ifdef WALBERLA_BUILD_WITH_PYTHON - - template<typename UID, typename StringContainer> - Set< UID > uidSetFromStringContainer( const StringContainer & stringContainer ) - { - Set< UID > result; - result.insert( boost::python::stl_input_iterator< std::string >( stringContainer ), - boost::python::stl_input_iterator< std::string >( ) ); - return result; - } - template< typename FField> typename FField::value_type maskFromFlagList( const shared_ptr<StructuredBlockStorage> & bs, ConstBlockDataID flagFieldID, diff --git a/src/python_coupling/helper/BoostPythonHelpers.h b/src/python_coupling/helper/BoostPythonHelpers.h deleted file mode 100644 index 88c4fec8c..000000000 --- a/src/python_coupling/helper/BoostPythonHelpers.h +++ /dev/null @@ -1,106 +0,0 @@ - -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file CppPythonTypeEquality.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "core/DataTypes.h" -#include "python_coupling/PythonWrapper.h" - -#include <boost/python/converter/registry.hpp> - - -namespace walberla { -namespace python_coupling { - -#ifdef WALBERLA_CXX_COMPILER_IS_CLANG -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" -#endif - - // fallback - check boost python registry - template<typename T> - inline static bool isCppEqualToPythonType( PyTypeObject * obj) - { - boost::python::type_info info = boost::python::type_id<T>(); - const boost::python::converter::registration* reg = boost::python::converter::registry::query(info); - if (reg == NULL) - return false; - - try - { - reg->get_class_object(); - return ( reg->get_class_object() == obj ); - } - catch ( ... ) { - return false; - } - } - - // native data types - template<> inline bool isCppEqualToPythonType<bool> ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.bool_" - || n =="bool" ); } - - - template<> inline bool isCppEqualToPythonType<float> ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.float32" ); } - template<> bool inline isCppEqualToPythonType<double>( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.float64" - || n == "numpy.float_" - || n =="float"); } - - - template<> inline bool isCppEqualToPythonType<uint8_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.uint8" ); } - template<> inline bool isCppEqualToPythonType<uint16_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.uint16" ); } - template<> inline bool isCppEqualToPythonType<uint32_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.uint32" ); } - template<> inline bool isCppEqualToPythonType<uint64_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.uint64" ); } - - - template<> inline bool isCppEqualToPythonType<int8_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.int8" ); } - template<> inline bool isCppEqualToPythonType<int16_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.int16" ); } - template<> inline bool isCppEqualToPythonType<int32_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.int32" ); } - template<> inline bool isCppEqualToPythonType<int64_t > ( PyTypeObject * o) { std::string n( o->tp_name ); return ( n == "numpy.int64" - || n == "int" ); } - -#ifdef WALBERLA_CXX_COMPILER_IS_CLANG -#pragma clang diagnostic pop -#endif - - - template< typename T > - bool isTypeRegisteredInBoostPython( ) - { - boost::python::type_info info = boost::python::type_id<T>(); - const boost::python::converter::registration* reg = boost::python::converter::registry::query(info); - - try { - reg->get_class_object(); - } - catch( ... ) { - PyErr_Clear(); - return false; - } - PyErr_Clear(); - return (reg != NULL); - } - -} // namespace python_coupling -} // namespace walberla - - diff --git a/src/python_coupling/helper/ConfigFromDict.cpp b/src/python_coupling/helper/ConfigFromDict.cpp index 5755373ea..e35ca0fd5 100644 --- a/src/python_coupling/helper/ConfigFromDict.cpp +++ b/src/python_coupling/helper/ConfigFromDict.cpp @@ -16,6 +16,7 @@ //! \file ConfigFromDict.cpp //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -37,59 +38,55 @@ void handlePythonBooleans( std::string & value) { if ( value=="False") value="0"; } -void configFromPythonDict( config::Config::Block & block, boost::python::dict & pythonDict ) -{ - using namespace boost::python; - - boost::python::list keys = pythonDict.keys(); - keys.sort(); +namespace py = pybind11; - for (int i = 0; i < boost::python::len( keys ); ++i) +void configFromPythonDict( config::Config::Block & block, py::dict & pythonDict ) +{ + for (auto item : pythonDict) { - // Extract key - boost::python::extract<std::string> extracted_key( keys[i] ); - if( !extracted_key.check() ) { + // py::print(item); + if( py::isinstance<std::string>(item.first) ) { WALBERLA_LOG_WARNING( "Detected non-string key in waLBerla configuration" ); continue; } - std::string key = extracted_key; - // Extract value - extract<std::string> extracted_str_val ( pythonDict[key] ); - extract<dict> extracted_dict_val ( pythonDict[key] ); - extract<list> extracted_list_val ( pythonDict[key] ); - extract<tuple> extracted_tuple_val ( pythonDict[key] ); + std::string key = py::str(item.first); try { - if( extracted_str_val.check() ){ - std::string value = extracted_str_val; + if( py::isinstance<std::string>(item.second) ){ + std::string value = py::str(item.second); handlePythonBooleans( value ); block.addParameter( key, value ); } - else if ( extracted_dict_val.check() ) + else if ( py::isinstance<py::dict>(item.second) ) { walberla::config::Config::Block & childBlock = block.createBlock( key ); - dict childDict = extracted_dict_val; - configFromPythonDict( childBlock, childDict ); + py::dict childDict = py::dict(pythonDict[key.c_str()]); + configFromPythonDict( childBlock, childDict); } - else if ( extracted_list_val.check() ) + else if ( py::isinstance<py::list>(item.second) ) { - list childList = extracted_list_val; - for( int l=0; l < len( childList ); ++l ) { + py::list childList = py::list(pythonDict[key.c_str()]); + for(py::size_t i = 0; i < childList.size(); ++i){ walberla::config::Config::Block & childBlock = block.createBlock( key ); - dict d = extract<dict>( childList[l] ); + py::dict d = py::dict(childList[i]); configFromPythonDict( childBlock, d ); } } - else if ( extracted_tuple_val.check() ) + else if ( py::isinstance<py::tuple>(item.second) ) { std::stringstream ss; - tuple childTuple = extracted_tuple_val; + py::tuple childTuple = py::tuple(pythonDict[key.c_str()]); + + WALBERLA_ASSERT(len(childTuple) == 2 || len(childTuple) == 3, + "Config problem: " << key << ": Python tuples are mapped to walberla::Vector2 or Vector3. \n" << + "So only tuples of size 2 or 3 are supported! Option " << key << " is ignored ") + if ( len(childTuple) == 2 ) { - std::string e0 = extract<std::string>( childTuple[0].attr("__str__" )() ); - std::string e1 = extract<std::string>( childTuple[1].attr("__str__" )() ); + std::string e0 = py::str( childTuple[0].attr("__str__" )() ); + std::string e1 = py::str( childTuple[1].attr("__str__" )() ); handlePythonBooleans( e0 ); handlePythonBooleans( e1 ); ss << "< " << e0 << " , " << e1 << " > "; @@ -97,40 +94,32 @@ void configFromPythonDict( config::Config::Block & block, boost::python::dict & } else if ( len(childTuple) == 3) { - std::string e0 = extract<std::string>( childTuple[0].attr("__str__" )() ); - std::string e1 = extract<std::string>( childTuple[1].attr("__str__" )() ); - std::string e2 = extract<std::string>( childTuple[2].attr("__str__" )() ); + std::string e0 = py::str( childTuple[0].attr("__str__" )() ); + std::string e1 = py::str( childTuple[1].attr("__str__" )() ); + std::string e2 = py::str( childTuple[2].attr("__str__" )() ); handlePythonBooleans( e0 ); handlePythonBooleans( e1 ); handlePythonBooleans( e2 ); - ss << "< " << e0 << " , " << e1 << ", " << e2 << " > "; + ss << "< " << e0 << ", " << e1 << ", " << e2 << " > "; block.addParameter( key, ss.str() ); } - else - { - WALBERLA_LOG_WARNING( "Config problem: " << key << ": Python tuples are mapped to walberla::Vector2 or Vector3. \n" << - "So only tuples of size 2 or 3 are supported! Option " << key << " is ignored "); - - } } else { // if value is not a string try to convert it - std::string value = extract<std::string>( pythonDict[key].attr("__str__" )() ); + std::string value = py::str( pythonDict[key.c_str()].attr("__str__" )() ); block.addParameter ( key, value ); } } - catch ( error_already_set & ) { + catch ( py::error_already_set & ) { WALBERLA_LOG_WARNING ( "Error when reading configuration option " << key << ". Could not be converted to string."); } } } -shared_ptr<Config> configFromPythonDict( boost::python::dict & pythonDict ) +shared_ptr<Config> configFromPythonDict( py::dict & pythonDict ) { - using namespace boost::python; - shared_ptr<Config> config = make_shared<Config>(); configFromPythonDict( config->getWritableGlobalBlock(), pythonDict ); return config; diff --git a/src/python_coupling/helper/ConfigFromDict.h b/src/python_coupling/helper/ConfigFromDict.h index e1bef7f27..146ebf0fc 100644 --- a/src/python_coupling/helper/ConfigFromDict.h +++ b/src/python_coupling/helper/ConfigFromDict.h @@ -16,6 +16,7 @@ //! \file ConfigFromDict.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -37,7 +38,7 @@ namespace python_coupling { /*! Converts a python dictionary to a config::Block (recursively) */ //******************************************************************************************************************* - void configFromPythonDict( config::Config::Block & result, boost::python::dict & pythonDict ); + void configFromPythonDict( config::Config::Block & result, py::dict & pythonDict ); @@ -45,7 +46,7 @@ namespace python_coupling { /*! Converts a python dictionary to a waLBerla config object */ //******************************************************************************************************************* - shared_ptr<Config> configFromPythonDict( boost::python::dict & pythonDict ); + shared_ptr<Config> configFromPythonDict( py::dict & pythonDict ); diff --git a/src/python_coupling/helper/ExceptionHandling.h b/src/python_coupling/helper/ExceptionHandling.h deleted file mode 100644 index e17ebdfd0..000000000 --- a/src/python_coupling/helper/ExceptionHandling.h +++ /dev/null @@ -1,71 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ExceptionDecode.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include <string> - - -namespace walberla { -namespace python_coupling { - - // Call this function when a boost::python::already_set exception was caught - // returns formatted error string with traceback - inline std::string decodeException() - { - namespace bp = boost::python; - - PyObject *exc,*val,*tb; - - bp::object formatted_list, formatted; - PyErr_Fetch(&exc,&val,&tb); - PyErr_NormalizeException(&exc, &val, &tb); - bp::handle<> hexc( exc ); - bp::handle<> hval( bp::allow_null(val) ); - bp::handle<> htb ( bp::allow_null(tb) ); - bp::object traceback( bp::import("traceback")); - - if (!tb) { - bp::object format_exception_only( traceback.attr("format_exception_only")); - formatted_list = format_exception_only(hexc,hval); - } else { - bp::object format_exception(traceback.attr("format_exception")); - formatted_list = format_exception(hexc,hval,htb); - } - formatted = bp::str("").join(formatted_list); - return bp::extract<std::string>(formatted); - } - - - inline void terminateOnPythonException( const std::string message ) - { - if (PyErr_Occurred()) { - std::string decodedException = decodeException(); - WALBERLA_ABORT_NO_DEBUG_INFO( message << "\n\n" << decodedException ); - } - WALBERLA_ABORT_NO_DEBUG_INFO( message << " (unable to decode Python exception) " ); - } - - -} // namespace python_coupling -} // namespace walberla - - diff --git a/src/python_coupling/helper/ModuleInit.cpp b/src/python_coupling/helper/ModuleInit.cpp index e917ff50b..3198acfc3 100644 --- a/src/python_coupling/helper/ModuleInit.cpp +++ b/src/python_coupling/helper/ModuleInit.cpp @@ -16,14 +16,14 @@ //! \file ModuleInit.cpp //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #include "ModuleInit.h" -#include "waLBerlaDefinitions.h" +#include "core/debug/CheckFunctions.h" #include "core/mpi/MPIManager.h" #include "core/Abort.h" -#include "core/debug/CheckFunctions.h" // Workaround for OpenMPI library: it dynamically loads plugins which causes trouble when walberla itself is a shared lib #if defined(OPEN_MPI) && !defined(_WIN32) && OMPI_MAJOR_VERSION < 3 diff --git a/src/python_coupling/helper/ModuleInit.h b/src/python_coupling/helper/ModuleInit.h index e771bb86c..e85c45d6c 100644 --- a/src/python_coupling/helper/ModuleInit.h +++ b/src/python_coupling/helper/ModuleInit.h @@ -16,6 +16,7 @@ //! \file ModuleInit.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== diff --git a/src/python_coupling/helper/ModuleScope.h b/src/python_coupling/helper/ModuleScope.h deleted file mode 100644 index 09a619781..000000000 --- a/src/python_coupling/helper/ModuleScope.h +++ /dev/null @@ -1,56 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file ModuleScope.h -//! \ingroup python_coupling -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "python_coupling/PythonWrapper.h" - -#include <string> - -namespace walberla { -namespace python_coupling { - - - class ModuleScope : public boost::python::scope - { - public: - ModuleScope( const std::string & name) - : boost::python::scope ( ModuleScope::createNew( name ) ) - {} - - private: - static boost::python::object createNew ( const std::string & name ) - { - using namespace boost::python; - object module( handle<>( borrowed(PyImport_AddModule( name.c_str() ) ) ) ); - scope().attr( name.c_str() ) = module; - return module; - } - - }; - - - - -} // namespace python_coupling -} // namespace walberla - - diff --git a/src/python_coupling/helper/MplHelpers.h b/src/python_coupling/helper/MplHelpers.h index d9b008664..0a5a1d9bd 100644 --- a/src/python_coupling/helper/MplHelpers.h +++ b/src/python_coupling/helper/MplHelpers.h @@ -16,6 +16,7 @@ //! \file MplHelpers.h //! \ingroup python_export //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== @@ -23,11 +24,6 @@ #include "domain_decomposition/IBlock.h" -#include <boost/mpl/for_each.hpp> -#include <boost/mpl/lambda.hpp> -#include <boost/mpl/pair.hpp> -#include <boost/mpl/transform.hpp> - #include <functional> #include <map> @@ -37,23 +33,6 @@ namespace walberla { namespace python_coupling { -template <typename V, typename T, typename Result> -struct list_of_pairs - : boost::mpl::fold<V, Result, - boost::mpl::push_back<boost::mpl::_1, boost::mpl::pair<T, boost::mpl::_2> > > -{}; - -template<typename V1, typename V2> -struct combine_vectors -: boost::mpl::fold< - V1, - boost::mpl::vector<>, - boost::mpl::lambda<list_of_pairs<V2,boost::mpl::_2, boost::mpl::_1> > ->::type -{}; - - - template <typename T> struct NonCopyableWrap {}; @@ -78,15 +57,20 @@ struct NonCopyableWrap {}; -template< typename Sequence, typename F > +template< typename F > +void for_each_noncopyable_type( const F & ) +{} + +template< typename Type, typename... Types, typename F > void for_each_noncopyable_type( const F & f) { - boost::mpl::for_each< Sequence, NonCopyableWrap< boost::mpl::placeholders::_1> > ( f ); + f(NonCopyableWrap<Type>()); + for_each_noncopyable_type<Types...>(f); } -template<typename FieldTypeList, typename Exporter> +template<typename Exporter, typename... FieldTypes> class Dispatcher { public: @@ -102,7 +86,7 @@ public: return map_[ blockDataID ]; Exporter exporter( block_, blockDataID ); - for_each_noncopyable_type< FieldTypeList> ( std::ref(exporter) ); + for_each_noncopyable_type< FieldTypes...> ( std::ref(exporter) ); map_[ blockDataID ] = exporter.result; return exporter.result; } diff --git a/src/python_coupling/helper/OwningIterator.h b/src/python_coupling/helper/OwningIterator.h new file mode 100644 index 000000000..f384acca4 --- /dev/null +++ b/src/python_coupling/helper/OwningIterator.h @@ -0,0 +1,74 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file OwningIterator.h +//! \ingroup python_coupling +//! \author Michael Kuron <mkuron@icp.uni-stuttgart.de> +// +//====================================================================================================================== + +#pragma once + +#include <mutex> +#include <pybind11/pybind11.h> + +namespace walberla { +namespace python_coupling { + +namespace py = pybind11; + +namespace detail { + +template <typename T, py::return_value_policy Policy> +struct owning_iterator_state { + owning_iterator_state(T _obj) + : obj(_obj), it(obj.begin()), first_or_done(true) {} + T obj; + typename T::iterator it; + bool first_or_done; + static std::once_flag registered; +}; + +template <typename T, py::return_value_policy Policy> +std::once_flag owning_iterator_state<T, Policy>::registered; + +} // namespace detail + +template <py::return_value_policy Policy = py::return_value_policy::reference_internal, + typename T> +py::iterator make_owning_iterator(T obj) { + using state = detail::owning_iterator_state<T, Policy>; + + std::call_once(state::registered, []() { + py::class_<state>(py::handle(), "owning_iterator", py::module_local()) + .def("__iter__", [](state &s) -> state& { return s; }) + .def("__next__", [](state &s) -> typename T::value_type { + if (!s.first_or_done) + ++s.it; + else + s.first_or_done = false; + if (s.it == s.obj.end()) { + s.first_or_done = true; + throw py::stop_iteration(); + } + return *s.it; + }, py::keep_alive< 0, 1 >(), Policy); + }); + + return cast(state(obj)); +} + +} // namespace python_coupling +} // namespace walberla diff --git a/src/python_coupling/helper/PybindHelper.h b/src/python_coupling/helper/PybindHelper.h new file mode 100644 index 000000000..d43c560cc --- /dev/null +++ b/src/python_coupling/helper/PybindHelper.h @@ -0,0 +1,65 @@ + +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file CppPythonTypeEquality.h +//! \ingroup python_coupling +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "python_coupling/PythonWrapper.h" + +namespace walberla { +namespace python_coupling { + +#ifdef WALBERLA_CXX_COMPILER_IS_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +// fallback - check for bool +template<typename T> +inline static bool isCppEqualToPythonType( std::string n ) +{ + return ( n == "numpy.bool_" || n =="bool" ); +} + +// native data types +template<> inline bool isCppEqualToPythonType<float> ( std::string n) {return ( n == "numpy.float32" || n =="float32" );} +template<> inline bool isCppEqualToPythonType<double>( std::string n) {return ( n == "numpy.float64" || n == "numpy.float_" || n =="float64" || n=="float");} + + +template<> inline bool isCppEqualToPythonType<uint8_t > ( std::string n) {return ( n == "numpy.uint8" || n =="uint8" );} +template<> inline bool isCppEqualToPythonType<uint16_t > ( std::string n) {return ( n == "numpy.uint16"|| n =="uint16" );} +template<> inline bool isCppEqualToPythonType<uint32_t > ( std::string n) {return ( n == "numpy.uint32"|| n =="uint32" );} +template<> inline bool isCppEqualToPythonType<uint64_t > ( std::string n) {return ( n == "numpy.uint64"|| n =="uint64" );} + + +template<> inline bool isCppEqualToPythonType<int8_t > ( std::string n) {return ( n == "numpy.int8" || n =="int8");} +template<> inline bool isCppEqualToPythonType<int16_t > ( std::string n) {return ( n == "numpy.int16" || n =="int16");} +template<> inline bool isCppEqualToPythonType<int32_t > ( std::string n) {return ( n == "numpy.int32" || n =="int32");} +template<> inline bool isCppEqualToPythonType<int64_t > ( std::string n) {return ( n == "numpy.int64" || n =="int64" || n == "int" );} + +#ifdef WALBERLA_CXX_COMPILER_IS_CLANG +#pragma clang diagnostic pop +#endif + +} // namespace python_coupling +} // namespace walberla diff --git a/src/python_coupling/helper/PythonIterableToStdVector.h b/src/python_coupling/helper/PythonIterableToStdVector.h index 5a759aedf..71583ec21 100644 --- a/src/python_coupling/helper/PythonIterableToStdVector.h +++ b/src/python_coupling/helper/PythonIterableToStdVector.h @@ -16,26 +16,30 @@ //! \file PythonIterableToStdVector.h.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #pragma once #include <vector> +#include "pybind11/numpy.h" namespace walberla { namespace python_coupling { +namespace py = pybind11; template< typename T > + inline -std::vector< T > pythonIterableToStdVector( const boost::python::object& iterable ) +std::vector< T > pythonIterableToStdVector( const py::object& iterable ) { - return std::vector< T >( boost::python::stl_input_iterator< T >( iterable ), - boost::python::stl_input_iterator< T >( ) ); + return py::cast<std::vector<T>>(py::array(iterable)); } + } // namespace python_coupling } // namespace walberla diff --git a/src/python_coupling/helper/SharedPtrDeleter.h b/src/python_coupling/helper/SharedPtrDeleter.h deleted file mode 100644 index a946cc038..000000000 --- a/src/python_coupling/helper/SharedPtrDeleter.h +++ /dev/null @@ -1,65 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file SharedPtrDeleter.h -//! \ingroup python_export -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "python_coupling/PythonWrapper.h" - -namespace walberla { -namespace python_coupling { - - -namespace internal -{ - -template<typename T> -class SharedPtrDeleterTiedToPythonObject -{ -public: - SharedPtrDeleterTiedToPythonObject( PyObject *object ) : object_( object ) - { - } - - void operator()( T * ) - { - Py_DECREF( object_ ); - } - -private: - PyObject *object_; -}; - -} // namespace internal - - -template<typename T> -shared_ptr<T> createSharedPtrFromPythonObject(boost::python::object pythonObject) { - T * ptr = boost::python::extract<T*>( pythonObject); - auto deleter = internal::SharedPtrDeleterTiedToPythonObject<T>(pythonObject.ptr()); - Py_INCREF(pythonObject.ptr()); - return shared_ptr<T>(ptr, deleter); -} - - -} // namespace python_coupling -} // namespace walberla - - diff --git a/src/python_coupling/helper/SliceToCellInterval.h b/src/python_coupling/helper/SliceToCellInterval.h index d8ce96829..d988c1c74 100644 --- a/src/python_coupling/helper/SliceToCellInterval.h +++ b/src/python_coupling/helper/SliceToCellInterval.h @@ -16,205 +16,94 @@ //! \file SliceToCellInterval.h //! \ingroup python_coupling //! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #pragma once -#include "python_coupling/PythonWrapper.h" #include "core/cell/CellInterval.h" -#include "domain_decomposition/StructuredBlockStorage.h" -#include <boost/python/slice.hpp> - -namespace walberla { -namespace python_coupling { - - - namespace internal - { - inline cell_idx_t normalizeIdx( boost::python::object pyIndex, uint_t coordinateSize ) - { - using namespace boost::python; - cell_idx_t index; - if ( extract<cell_idx_t>( pyIndex ).check() ) - index = extract<cell_idx_t> ( pyIndex ); - else if ( extract<double>( pyIndex ).check() ) - index = cell_idx_c( double ( extract<double>( pyIndex ) ) * double( coordinateSize) ); - else { - PyErr_SetString( PyExc_IndexError, "Incompatible index data type" ); - throw error_already_set(); - } - - if ( index < 0 ) - return cell_idx_c(coordinateSize) + 1 + index; - else - return index; - } - - } // namespace internal +#include "domain_decomposition/StructuredBlockStorage.h" +#include "python_coupling/PythonWrapper.h" +namespace py = pybind11; +namespace walberla +{ +namespace python_coupling +{ +namespace internal +{ +inline cell_idx_t normalizeIdx(py::object pyIndex, uint_t coordinateSize) +{ + cell_idx_t index; + + try { + index = py::cast< cell_idx_t >(pyIndex); + } + catch (py::error_already_set & ){ + throw py::cast_error("Incompatible index data type"); + } - //******************************************************************************************************************* - /*! Creates a CellInterval as subset from the complete domain-cell-bounding-box based on a Python slice - * - * Example: Python Slice: [ :, 3, -1 ] and a domain size of ( 3,4,5 ) - * - x coordinate is the complete valid x-range indicated by the semicolon: i.e. [0,3) - * - y coordinate is just a normal index i.e. the range from [3,4) - * - z coordiante is the first valid coordinate from the back [4,5) - * - * Python slices are tuples with slice classes as entry. Each slice has start, stop and step. - * Steps are not supported since they can not be encoded in a CellInterval - */ - //******************************************************************************************************************* - inline CellInterval globalPythonSliceToCellInterval( const shared_ptr<StructuredBlockStorage> & blocks, - boost::python::tuple indexTuple ) + if (index < 0) + return cell_idx_c(coordinateSize) + 1 + index; + else + return index; +} + +} // namespace internal + +//******************************************************************************************************************* +/*! Creates a CellInterval as subset from the complete domain-cell-bounding-box based on a Python slice + * + * Example: Python Slice: [ :, 3, -1 ] and a domain size of ( 3,4,5 ) + * - x coordinate is the complete valid x-range indicated by the semicolon: i.e. [0,3) + * - y coordinate is just a normal index i.e. the range from [3,4) + * - z coordiante is the first valid coordinate from the back [4,5) + * + * Python slices are tuples with slice classes as entry. Each slice has start, stop and step. + * Steps are not supported since they can not be encoded in a CellInterval + */ +//******************************************************************************************************************* +inline CellInterval globalPythonSliceToCellInterval(const shared_ptr< StructuredBlockStorage >& blocks, + py::tuple indexTuple) +{ + using internal::normalizeIdx; + + CellInterval bounds = blocks->getDomainCellBB(); + + if (len(indexTuple) != 3) { - using namespace boost::python; - using internal::normalizeIdx; - - CellInterval bounds = blocks->getDomainCellBB(); - - if ( len(indexTuple) != 3 ) - { - PyErr_SetString( PyExc_IndexError, "Slice needs three components" ); - throw error_already_set(); - } - - CellInterval interval; - for( uint_t i=0; i<3; ++i ) - { - if( ! extract< slice >(indexTuple[i]).check() ) - { - cell_idx_t idx = normalizeIdx( indexTuple[i], uint_c( bounds.max()[i] ) ); - interval.min()[i] = idx; - interval.max()[i] = idx; - } - else if ( extract< slice >(indexTuple[i]).check() ) - { - slice s = extract< slice >(indexTuple[i]); - - // Min - if ( s.start() == object() ) - interval.min()[i] = bounds.min()[i]; - else - interval.min()[i] = normalizeIdx( s.start(), uint_c( bounds.max()[i] ) ); - - // Max - if ( s.stop() == object() ) - interval.max()[i] = bounds.max()[i]; - else - interval.max()[i] = normalizeIdx( s.stop(), uint_c( bounds.max()[i] ) ); - - if ( s.step() != object() ) { - PyErr_SetString( PyExc_IndexError, "Steps in slice not supported." ); - throw error_already_set(); - } - } - } - return interval; + throw py::index_error("Slice needs three components"); } - - - //******************************************************************************************************************* - /*! Creates a CellInterval based on a Python Slice as subset of a field - * - * Similar to globalPythonSliceToCellInterval() with the following additional features: - * - slice may have a forth component: [ :, 3, -1, 'g' ] with the only valid entry 'g' for ghost layers - * - if this ghost layer marker is present, coordinate 0 addresses the outermost ghost layer, otherwise the - * first inner cell is addressed - */ - //******************************************************************************************************************* - template<typename Field_T> - CellInterval localPythonSliceToCellInterval( const Field_T & field, - boost::python::tuple indexTuple ) + CellInterval interval; + for (uint_t i = 0; i < 3; ++i) { - using namespace boost::python; - using internal::normalizeIdx; - - bool withGhostLayer=false; - - if ( len(indexTuple) != 3 ) - { - if ( len(indexTuple) == 4 ) - { - std::string marker = extract<std::string>( indexTuple[3]); - if ( marker == std::string("g") ) - withGhostLayer = true; - else - { - PyErr_SetString( PyExc_IndexError, "Unknown marker in slice" ); - throw error_already_set(); - } - } - else - { - PyErr_SetString( PyExc_IndexError, "Slice needs three components ( + optional ghost layer marker )" ); - throw error_already_set(); - } - } - - cell_idx_t gl = cell_idx_c( field.nrOfGhostLayers() ); - - CellInterval bounds;; - if ( withGhostLayer ) + if (!py::isinstance< py::slice >(indexTuple[i])) { - bounds = field.xyzSizeWithGhostLayer(); - bounds.shift( gl,gl,gl ); + cell_idx_t idx = normalizeIdx(indexTuple[i], uint_c(bounds.max()[i])); + interval.min()[i] = idx; + interval.max()[i] = idx; } - else - bounds = field.xyzSize(); - - - CellInterval interval; - - for( uint_t i=0; i<3; ++i ) + else if (py::isinstance< py::slice >(indexTuple[i])) { - if( ! extract< slice >(indexTuple[i]).check() ) - { - interval.min()[i] = normalizeIdx( indexTuple[i], uint_c(bounds.max()[i]) ); - interval.max()[i] = normalizeIdx( indexTuple[i], uint_c(bounds.max()[i]) ); - } + py::slice s = py::cast< py::slice >(indexTuple[i]); + // Min + if ( py::isinstance< py::none >(s.attr("start")) ) + interval.min()[i] = bounds.min()[i]; else - { - slice s = extract< slice >(indexTuple[i]); + interval.min()[i] = normalizeIdx( s.attr("start"), uint_c( bounds.min()[i] ) ); - // Min - if ( s.start() == object() ) - interval.min()[i] = bounds.min()[i]; - else - interval.min()[i] = normalizeIdx( s.start(), uint_c(bounds.max()[i]) ); - - // Max - if ( s.stop() == object() ) - interval.max()[i] = bounds.max()[i]; - else - interval.max()[i] = normalizeIdx( s.stop(), uint_c(bounds.max()[i]) ); - - if ( s.step() != object() ) { - PyErr_SetString( PyExc_IndexError, "Steps in slice not supported." ); - throw error_already_set(); - } - } - } - - if ( withGhostLayer ) - interval.shift( -gl,-gl,-gl ); - - // Range check - if ( ! field.xyzAllocSize().contains( interval ) ) { - PyErr_SetString( PyExc_IndexError, "Index out of bounds." ); - throw error_already_set(); + // Max + if ( py::isinstance< py::none >(s.attr("stop")) ) + interval.max()[i] = bounds.max()[i]; + else + interval.max()[i] = normalizeIdx( s.attr("stop"), uint_c( bounds.max()[i] ) ); } - - return interval; } - - - + return interval; +} } // namespace python_coupling } // namespace walberla - - diff --git a/src/timeloop/python/Exports.cpp b/src/timeloop/python/Exports.cpp deleted file mode 100644 index 3411e7f29..000000000 --- a/src/timeloop/python/Exports.cpp +++ /dev/null @@ -1,89 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.cpp -//! \ingroup timeloop -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -// Do not reorder includes - the include order is important -#include "python_coupling/PythonWrapper.h" - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -#include "python_coupling/Manager.h" -#include "python_coupling/helper/ModuleScope.h" - -#include "timeloop/Timeloop.h" -#include "timeloop/SweepTimeloop.h" - -using namespace boost::python; - - -namespace walberla { -namespace timeloop { - -#ifdef WALBERLA_CXX_COMPILER_IS_GNU -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" -#endif -struct ITimeloopWrap : public ITimeloop, public wrapper<ITimeloop> -{ - void run() override { this->get_override( "run" )(); } - void singleStep() override { this->get_override( "singleStep" )(); } - void stop() override { this->get_override( "stop" )(); } - void synchronizedStop( bool s ) override { this->get_override( "synchronizedStop" )(s); } - void setCurrentTimeStep( uint_t ts) override { this->get_override( "setCurrentTimeStep" )(ts); } - uint_t getCurrentTimeStep() const override { return this->get_override( "getCurrentTimeStep" )(); } - uint_t getNrOfTimeSteps() const override { return this->get_override( "getNrOfTimeSteps" )(); } -}; - -#ifdef WALBERLA_CXX_COMPILER_IS_GNU -#pragma GCC diagnostic pop -#endif - -void exportModuleToPython() -{ - python_coupling::ModuleScope timeloopModule( "timeloop" ); - - void ( Timeloop::*p_run1) ( const bool ) = &Timeloop::run; - void ( Timeloop::*p_run2) ( WcTimingPool & , const bool ) = &Timeloop::run; - - class_<ITimeloopWrap, boost::noncopyable > ("ITimeloop" ) - .def( "getCurrentTimeStep", pure_virtual( &ITimeloop::getCurrentTimeStep ) ) - .def( "getNrOfTimeSteps", pure_virtual( &ITimeloop::getNrOfTimeSteps ) ) - .def( "stop", pure_virtual( &ITimeloop::stop ) ) - .def( "synchronizedStop", pure_virtual( &ITimeloop::synchronizedStop ) ) - .def( "run", pure_virtual( &ITimeloop::run ) ) - ; - - class_<Timeloop, bases<ITimeloop>, boost::noncopyable>( "CppTimeloop", no_init ) - .def( "run", p_run1, args("logTimeStep") = true ) - .def( "run", p_run2, ( args("timingPool"), args("logTimeStep") = true ) ) - ; - class_< SweepTimeloop , bases<Timeloop>, boost::noncopyable > ( "CppSweepTimeloop", no_init ) - .def( init< const shared_ptr<StructuredBlockStorage> & , uint_t > () ) - ; - -} - - -} // namespace timeloop -} // namespace walberla - - -#endif //WALBERLA_BUILD_WITH_PYTHON - diff --git a/src/timeloop/python/Exports.h b/src/timeloop/python/Exports.h deleted file mode 100644 index 4126ed455..000000000 --- a/src/timeloop/python/Exports.h +++ /dev/null @@ -1,41 +0,0 @@ -//====================================================================================================================== -// -// This file is part of waLBerla. waLBerla is free software: you can -// redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. -// -//! \file Exports.h -//! \ingroup timeloop -//! \author Martin Bauer <martin.bauer@fau.de> -// -//====================================================================================================================== - -#pragma once - -#include "waLBerlaDefinitions.h" - - -#ifdef WALBERLA_BUILD_WITH_PYTHON - -namespace walberla { -namespace timeloop { - - - - void exportModuleToPython(); - - -} // namespace timeloop -} // namespace walberla - - -#endif // WALBERLA_BUILD_WITH_PYTHON diff --git a/src/vtk/CMakeLists.txt b/src/vtk/CMakeLists.txt index a3b6bf30e..6a12fce9b 100644 --- a/src/vtk/CMakeLists.txt +++ b/src/vtk/CMakeLists.txt @@ -4,6 +4,6 @@ # ################################################################################################### -waLBerla_add_module( DEPENDS core domain_decomposition python_coupling ) +waLBerla_add_module( DEPENDS core blockforest domain_decomposition python_coupling ) ################################################################################################### diff --git a/src/walberla.h b/src/walberla.h index c6e9858ed..7136333e4 100644 --- a/src/walberla.h +++ b/src/walberla.h @@ -34,7 +34,6 @@ #include "pde/all.h" #include "pe_coupling/all.h" #include "postprocessing/all.h" -#include "python_coupling/all.h" #include "stencil/all.h" #include "timeloop/all.h" #include "vtk/all.h" diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index caf28285c..89583aadb 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -179,7 +179,11 @@ waLBerla_execute_test( NAME SweepTimeloopTimerReduction PROCESSES 9 ) # core # ######## -waLBerla_compile_test( FILES AllHeaderTest.cpp DEPENDS blockforest field geometry pe pe_coupling python_coupling ) +if ( WALBERLA_BUILD_WITH_PYTHON ) + waLBerla_compile_test( FILES AllHeaderTest.cpp DEPENDS blockforest field geometry pe pe_coupling python_coupling ) +else() + waLBerla_compile_test( FILES AllHeaderTest.cpp DEPENDS blockforest field geometry pe pe_coupling ) +endif() waLBerla_execute_test( NAME AllHeaderTest ) waLBerla_compile_test( FILES ConcatIterator.cpp ) diff --git a/tests/pe/SyncEquivalence.cpp b/tests/pe/SyncEquivalence.cpp index d17e2f0f1..6c0318018 100644 --- a/tests/pe/SyncEquivalence.cpp +++ b/tests/pe/SyncEquivalence.cpp @@ -18,8 +18,6 @@ // //====================================================================================================================== -#include "python_coupling/DictWrapper.h" - #include "blockforest/all.h" #include "core/all.h" #include "domain_decomposition/all.h" diff --git a/tests/python_coupling/BasicDatatypeTest.py b/tests/python_coupling/BasicDatatypeTest.py deleted file mode 100644 index bd5deff65..000000000 --- a/tests/python_coupling/BasicDatatypeTest.py +++ /dev/null @@ -1,26 +0,0 @@ -import unittest -import waLBerla as wlb - - -class BasicDatatypesTest(unittest.TestCase): - - def test_CellInterval(self): - ci1 = wlb.CellInterval(0, 0, 0, 5, 5, 5) - ci2 = wlb.CellInterval([0] * 3, [5] * 3) - self.assertEqual(ci1, ci2, "Equality comparison of CellIntervals failed.") - self.assertFalse(ci1 != ci2, "Inequality check for CellIntervals wrong ") - - self.assertEqual(ci1.min, (0, 0, 0), "CellInterval min wrong") - self.assertEqual(ci1.max, (5, 5, 5), "CellInterval max wrong") - - self.assertFalse(ci1.empty()) - - ci1.intersect(ci2) - self.assertTrue(ci1.contains(ci2)) - - ci2.expand(1) - self.assertFalse(ci1.contains(ci2)) - - def test_AABB(self): - aabb1 = wlb.AABB(0, 0, 0, 5, 5, 5) # noqa: F841 - aabb2 = wlb.AABB([0] * 3, [5] * 3) # noqa: F841 diff --git a/tests/python_coupling/CMakeLists.txt b/tests/python_coupling/CMakeLists.txt index 8e7c48fc9..462c2b8f2 100644 --- a/tests/python_coupling/CMakeLists.txt +++ b/tests/python_coupling/CMakeLists.txt @@ -1,6 +1,6 @@ ################################################################################################### # -# Tests for timeloop module +# Tests for python coupling # ################################################################################################### @@ -13,9 +13,16 @@ if (WALBERLA_BUILD_WITH_PYTHON) waLBerla_compile_test( FILES CallbackTest.cpp DEPENDS blockforest field ) waLBerla_execute_test( NAME CallbackTest - COMMAND $<TARGET_FILE:CallbackTest> ${CMAKE_CURRENT_SOURCE_DIR}/CallbackTest.py ) + COMMAND $<TARGET_FILE:CallbackTest> ${CMAKE_CURRENT_SOURCE_DIR}/CallbackTest.py ) + set_target_properties(CallbackTest PROPERTIES CXX_VISIBILITY_PRESET hidden) waLBerla_compile_test( FILES FieldExportTest.cpp DEPENDS blockforest field ) waLBerla_execute_test( NAME FieldExportTest COMMAND $<TARGET_FILE:FieldExportTest> ${CMAKE_CURRENT_SOURCE_DIR}/FieldExportTest.py ) + set_target_properties(FieldExportTest PROPERTIES CXX_VISIBILITY_PRESET hidden) + + add_test( NAME PythonWalberlaTest + COMMAND python3 -m unittest discover waLBerla_tests/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/python/) + SET_TESTS_PROPERTIES(PythonWalberlaTest + PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/apps/pythonmodule:$PYTHONPATH") endif() diff --git a/tests/python_coupling/CallbackTest.cpp b/tests/python_coupling/CallbackTest.cpp index 95d210b76..08790f31f 100644 --- a/tests/python_coupling/CallbackTest.cpp +++ b/tests/python_coupling/CallbackTest.cpp @@ -13,33 +13,30 @@ // You should have received a copy of the GNU General Public License along // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // -//! \file CallbackTest.h -//! \author Martin Bauer <martin.bauer@fau.de> -// +//! \file CallbackTest.cpp +//! \author Markus Holzer <markus.holzer@fau.de> +//! //====================================================================================================================== -#include "python_coupling/PythonWrapper.h" +#include "blockforest/Initialization.h" -#include "core/debug/TestSubsystem.h" #include "core/Environment.h" +#include "core/debug/TestSubsystem.h" #include "field/Field.h" -#include "field/python/Exports.h" -#include "blockforest/Initialization.h" -#include "blockforest/python/Exports.h" -#include "python_coupling/PythonCallback.h" -#include "python_coupling/Shell.h" + #include "python_coupling/DictWrapper.h" #include "python_coupling/Manager.h" +#include "python_coupling/PythonCallback.h" +#include "python_coupling/export/FieldExports.h" using namespace walberla; int main( int argc, char ** argv ) { - typedef boost::mpl::vector< Field<int,1> > Fields; auto pythonManager = python_coupling::Manager::instance(); - pythonManager->addExporterFunction( field::exportModuleToPython<Fields> ); + pythonManager->addExporterFunction( field::exportModuleToPython<Field<int,1>> ); if ( argc != 2 ) { WALBERLA_ABORT_NO_DEBUG_INFO("Wrong parameter count: \nUsage: \n ./CallbackTest CallbackTest.py"); diff --git a/tests/python_coupling/CallbackTest.py b/tests/python_coupling/CallbackTest.py index e6aa0a7b3..95381f415 100644 --- a/tests/python_coupling/CallbackTest.py +++ b/tests/python_coupling/CallbackTest.py @@ -9,9 +9,9 @@ def someCallback(input1, input2): @waLBerla.callback("cb2") def fieldCallback(field): - npArray = waLBerla.field.toArray(field) - npArray[0, 0, 0] = 42 + numpy_array = waLBerla.field.toArray(field) + numpy_array[0, 0, 0] = 42 - npArrayGl = waLBerla.field.toArray(field, withGhostLayers=True) - print(npArrayGl.shape) - npArrayGl[0, 0, 0] = 5 + numpy_array_with_gl = waLBerla.field.toArray(field, with_ghost_layers=True) + print(numpy_array_with_gl.shape) + numpy_array_with_gl[0, 0, 0] = 5 diff --git a/tests/python_coupling/ConfigFromPythonTest.cpp b/tests/python_coupling/ConfigFromPythonTest.cpp index 527cc3e55..7b3437463 100644 --- a/tests/python_coupling/ConfigFromPythonTest.cpp +++ b/tests/python_coupling/ConfigFromPythonTest.cpp @@ -1,52 +1,65 @@ //====================================================================================================================== // -// This file is part of waLBerla. waLBerla is free software: you can +// This file is part of waLBerla. waLBerla is free software: you can // redistribute it and/or modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation, either version 3 of +// License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. -// -// waLBerla is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. -// +// // You should have received a copy of the GNU General Public License along // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // //! \file ConfigFromPythonTest.cpp //! \ingroup core -//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== #include "core/Abort.h" #include "core/debug/TestSubsystem.h" -#include "core/mpi/MPIManager.h" +#include "core/logging/Logging.h" +#include "core/math/Vector3.h" #include "core/mpi/Environment.h" + #include "python_coupling/CreateConfig.h" using namespace walberla; - -int main( int argc, char** argv ) +int main(int argc, char** argv) { debug::enterTestMode(); - mpi::Environment env( argc, argv ); + mpi::Environment env(argc, argv); - auto config = python_coupling::createConfigFromPythonScript( argv[1] ); + int counter = 0; + for (auto cfg = python_coupling::configBegin(argc, argv); cfg != python_coupling::configEnd(); ++cfg) + { + auto config = *cfg; + auto parameters = config->getOneBlock("DomainSetup"); + const int test_int = parameters.getParameter< int >("testInt"); + const std::string testString = parameters.getParameter< std::string >("testString"); + const real_t testDouble = parameters.getParameter< real_t >("testDouble"); + Vector3< real_t > testVector = parameters.getParameter< Vector3< real_t > >("testVector"); + const bool testBool = parameters.getParameter< bool >("testBool"); - WALBERLA_CHECK_EQUAL( argc, 2 ); + if (counter == 0) + WALBERLA_CHECK(test_int == 4) + else + WALBERLA_CHECK(test_int == 5) - config->listParameters(); - WALBERLA_CHECK_EQUAL ( int ( config->getParameter<int> ("testInt") ), 4 ); - WALBERLA_CHECK_EQUAL ( std::string( config->getParameter<std::string>("testString")), "someString" ); - WALBERLA_CHECK_FLOAT_EQUAL( double ( config->getParameter<real_t> ("testDouble")), real_t(42.42)); + counter++; + WALBERLA_CHECK(testString == "someString") + WALBERLA_CHECK(testDouble > 42 && testDouble < 43) + WALBERLA_CHECK(testVector == Vector3< real_t >(0.5, 0.5, 0.7)) + WALBERLA_CHECK(testBool == false) - auto subBlock = config->getBlock("subBlock"); - WALBERLA_CHECK_EQUAL ( std::string( subBlock.getParameter<std::string>("subKey1") ), std::string("abc") ); - WALBERLA_CHECK_EQUAL ( std::string( subBlock.getParameter<std::string>("subKey2") ), std::string("def") ); + WALBERLA_LOG_INFO_ON_ROOT(test_int) + } - return 0; + return EXIT_SUCCESS; } diff --git a/tests/python_coupling/ConfigFromPythonTest.py b/tests/python_coupling/ConfigFromPythonTest.py index 9c4a4f45a..d319203af 100644 --- a/tests/python_coupling/ConfigFromPythonTest.py +++ b/tests/python_coupling/ConfigFromPythonTest.py @@ -1,15 +1,27 @@ -import waLBerla - - -@waLBerla.callback("config") -def waLBerlaConfig(): - conf = { - 'testInt': 4, - 'testString': "someString", - 'testDouble': 42.42, - '44242': 'ohoh_IntegerKey', - 'subBlock': {'subKey1': 'abc', - 'subKey2': 'def' - } - } - return conf +import waLBerla as wlb + + +class Scenario: + def __init__(self, number): + self.testInt = number + self.testString = "someString" + self.testDouble = 42.42 + self.testVector = (0.5, 0.5, 0.7) + self.testBool = False + + @wlb.member_callback + def config(self): + return { + 'DomainSetup': { + 'testInt': self.testInt, + 'testDouble': self.testDouble, + 'testString': self.testString, + 'testVector': self.testVector, + 'testBool': self.testBool + } + } + + +scenarios = wlb.ScenarioManager() +scenarios.add(Scenario(4)) +scenarios.add(Scenario(5)) diff --git a/tests/python_coupling/FieldExportTest.cpp b/tests/python_coupling/FieldExportTest.cpp index 05997b58a..b78bc3682 100644 --- a/tests/python_coupling/FieldExportTest.cpp +++ b/tests/python_coupling/FieldExportTest.cpp @@ -13,28 +13,27 @@ // You should have received a copy of the GNU General Public License along // with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. // -//! \file FieldExportTest.h -//! \author Martin Bauer <martin.bauer@fau.de> +//! \file FieldExportTest.cpp +//! //! \author Markus Holzer <markus.holzer@fau.de> // //====================================================================================================================== -#include "python_coupling/PythonWrapper.h" +#include "blockforest/Initialization.h" #include "core/Environment.h" #include "core/debug/TestSubsystem.h" +#include "core/math/Random.h" #include "core/math/Vector2.h" #include "core/math/Vector3.h" -#include "core/math/Random.h" -#include "blockforest/Initialization.h" -#include "blockforest/python/Exports.h" -#include "field/python/Exports.h" +#include "python_coupling/DictWrapper.h" #include "python_coupling/Manager.h" #include "python_coupling/PythonCallback.h" -#include "python_coupling/DictWrapper.h" -#include "stencil/D2Q9.h" +#include "python_coupling/PythonWrapper.h" +#include "python_coupling/export/BlockForestExport.h" +#include "python_coupling/export/FieldExports.h" -#include <boost/mpl/vector.hpp> +#include "stencil/D2Q9.h" using namespace walberla; @@ -46,36 +45,29 @@ int main( int argc, char ** argv ) mpi::Environment mpiEnv( argc, argv ); auto pythonManager = python_coupling::Manager::instance(); - typedef boost::mpl::vector< - Field<Vector2<int>,1>, - Field<Vector3<int>,1>, - Field<int,2>, - Field<int,3> > FieldTypes; - - typedef boost::mpl::vector< stencil::D2Q9> Stencils; - pythonManager->addExporterFunction( field::exportModuleToPython<FieldTypes> ); - pythonManager->addBlockDataConversion< FieldTypes >() ; - pythonManager->addExporterFunction( blockforest::exportModuleToPython<Stencils> ); + pythonManager->addExporterFunction( field::exportModuleToPython<Field<int, 3>, Field<real_t, 3>> ); + pythonManager->addBlockDataConversion< Field<int, 3>, Field<real_t, 3> >() ; + pythonManager->addExporterFunction( blockforest::exportModuleToPython<stencil::D2Q9> ); shared_ptr< StructuredBlockForest > blocks = blockforest::createUniformBlockGrid( 1,1,1, 20,20,1, real_t(1.0), false, true,true,true ); - auto sca2FieldID = field::addToStorage< GhostLayerField<int,2> >( blocks, "sca2Field", int(0), field::fzyx, 1 ); - auto sca3FieldID = field::addToStorage< GhostLayerField<int,3> >( blocks, "sca3Field", int(0), field::fzyx, 1 ); + auto srcIntFieldID = field::addToStorage< GhostLayerField<int, 3> >( blocks, "srcIntFieldID", int(0), field::fzyx, 1 ); + auto dstIntFieldID = field::addToStorage< GhostLayerField<int, 3> >( blocks, "dstIntFieldID", int(0), field::fzyx, 1 ); - auto vec2FieldID = field::addToStorage< GhostLayerField<Vector2<int>,1> >( blocks, "vec2Field", Vector2<int>(), field::zyxf, 1 ); - auto vec3FieldID = field::addToStorage< GhostLayerField<Vector3<int>,1> >( blocks, "vec3Field", Vector3<int>(), field::zyxf, 1 ); + auto srcDoubleFieldID = field::addToStorage< GhostLayerField<real_t, 3> >( blocks, "srcDoubleFieldID", real_t(0.0), field::fzyx, 1 ); + auto dstDoubleFieldID = field::addToStorage< GhostLayerField<real_t, 3> >( blocks, "dstDoubleFieldID", real_t(0.0), field::fzyx, 1 ); // random init for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) { - auto sca2Field = blockIt->getData<GhostLayerField<int,2> >( sca2FieldID ); - auto sca3Field = blockIt->getData<GhostLayerField<int,3> >( sca3FieldID ); - for( auto cellIt = sca2Field->begin(); cellIt != sca2Field->end(); ++cellIt ) - *cellIt = math::intRandom( int(0), int(42) ); - for( auto cellIt = sca3Field->begin(); cellIt != sca3Field->end(); ++cellIt ) + auto srcIntField = blockIt->getData<GhostLayerField<int, 3> >( srcIntFieldID ); + auto srcDoubleField = blockIt->getData<GhostLayerField<real_t, 3> >( srcDoubleFieldID ); + for( auto cellIt = srcIntField->begin(); cellIt != srcIntField->end(); ++cellIt ) *cellIt = math::intRandom( int(0), int(42) ); + for( auto cellIt = srcDoubleField->begin(); cellIt != srcDoubleField->end(); ++cellIt ) + *cellIt = math::realRandom( real_t(0.0), real_t(42.0) ); } // call python function which should copy over the values to the Vector fields @@ -89,26 +81,26 @@ int main( int argc, char ** argv ) // check for equivalence for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) { - auto sca2Field = blockIt->getData<GhostLayerField<int,2> >( sca2FieldID ); - auto sca3Field = blockIt->getData<GhostLayerField<int,3> >( sca3FieldID ); - auto vec2Field = blockIt->getData<GhostLayerField<Vector2<int>,1 > >( vec2FieldID ); - auto vec3Field = blockIt->getData<GhostLayerField<Vector3<int>,1 > >( vec3FieldID ); + auto srcIntField = blockIt->getData<GhostLayerField<int, 3> >( srcIntFieldID ); + auto dstIntField = blockIt->getData<GhostLayerField<int, 3> >( dstIntFieldID ); + auto srcDoubleField = blockIt->getData<GhostLayerField<real_t, 3> >( srcDoubleFieldID ); + auto dstDoubleField = blockIt->getData<GhostLayerField<real_t, 3> >( dstDoubleFieldID ); { - for(cell_idx_t z = 0; z < cell_idx_c(sca2Field->zSize()); ++z) - for(cell_idx_t y = 0; y < cell_idx_c(sca2Field->zSize()); ++y) - for(cell_idx_t x = 0; x < cell_idx_c(sca2Field->zSize()); ++x) + for(cell_idx_t z = 0; z < cell_idx_c(srcIntField->zSize()); ++z) + for(cell_idx_t y = 0; y < cell_idx_c(srcIntField->zSize()); ++y) + for(cell_idx_t x = 0; x < cell_idx_c(srcIntField->zSize()); ++x) { - WALBERLA_CHECK_EQUAL( sca2Field->get(x,y,z, 0), vec2Field->get(x,y,z)[0] ); - WALBERLA_CHECK_EQUAL( sca2Field->get(x,y,z, 1), vec2Field->get(x,y,z)[1] ); + WALBERLA_CHECK_EQUAL( srcIntField->get(x,y,z, 0), dstIntField->get(x,y,z, 0) ); + WALBERLA_CHECK_EQUAL( srcIntField->get(x,y,z, 1), dstIntField->get(x,y,z, 1) ); } - for(cell_idx_t z = 0; z < cell_idx_c(sca3Field->zSize()); ++z) - for(cell_idx_t y = 0; y < cell_idx_c(sca3Field->zSize()); ++y) - for(cell_idx_t x = 0; x < cell_idx_c(sca3Field->zSize()); ++x) + for(cell_idx_t z = 0; z < cell_idx_c(srcDoubleField->zSize()); ++z) + for(cell_idx_t y = 0; y < cell_idx_c(srcDoubleField->zSize()); ++y) + for(cell_idx_t x = 0; x < cell_idx_c(srcDoubleField->zSize()); ++x) { - WALBERLA_CHECK_EQUAL( sca3Field->get(x,y,z, 0), vec3Field->get(x,y,z)[0] ); - WALBERLA_CHECK_EQUAL( sca3Field->get(x,y,z, 1), vec3Field->get(x,y,z)[1] ); - WALBERLA_CHECK_EQUAL( sca3Field->get(x,y,z, 2), vec3Field->get(x,y,z)[2] ); + WALBERLA_CHECK_FLOAT_EQUAL( srcDoubleField->get(x,y,z, 0), dstDoubleField->get(x,y,z, 0) ); + WALBERLA_CHECK_FLOAT_EQUAL( srcDoubleField->get(x,y,z, 1), dstDoubleField->get(x,y,z, 1) ); + WALBERLA_CHECK_FLOAT_EQUAL( srcDoubleField->get(x,y,z, 2), dstDoubleField->get(x,y,z, 2) ); } } } diff --git a/tests/python_coupling/FieldExportTest.py b/tests/python_coupling/FieldExportTest.py index 7790e9d2c..98da1c797 100644 --- a/tests/python_coupling/FieldExportTest.py +++ b/tests/python_coupling/FieldExportTest.py @@ -6,5 +6,5 @@ import numpy as np @waLBerla.callback("theCallback") def theCallback(blocks): for block in blocks: - np.copyto(toArray(block['vec2Field']), toArray(block['sca2Field'])) - np.copyto(toArray(block['vec3Field']), toArray(block['sca3Field'])) + np.copyto(toArray(block['srcIntFieldID']), toArray(block['dstIntFieldID'])) + np.copyto(toArray(block['srcDoubleFieldID']), toArray(block['dstDoubleFieldID'])) diff --git a/utilities/conda/walberla/bld.bat b/utilities/conda/walberla/bld.bat index ead78eca7..2b90c35b5 100644 --- a/utilities/conda/walberla/bld.bat +++ b/utilities/conda/walberla/bld.bat @@ -4,7 +4,6 @@ cd build set BOOST_ROOT=%PREFIX% cmake -LAH -G"Visual Studio 15 2017 Win64" ^ -DWALBERLA_BUILD_WITH_PYTHON=ON ^ - -DWALBERLA_BUILD_WITH_PYTHON_MODULE=ON ^ -DWALBERLA_BUILD_WITH_MPI=OFF ^ -DWALBERLA_BUILD_WITH_OPENMP=ON ^ -DPYTHON_EXECUTABLE="%PYTHON%" ^ diff --git a/utilities/conda/walberla/build.sh b/utilities/conda/walberla/build.sh index ac6ff0002..97256607b 100644 --- a/utilities/conda/walberla/build.sh +++ b/utilities/conda/walberla/build.sh @@ -5,8 +5,6 @@ cmake \ -DCMAKE_FIND_ROOT_PATH=${PREFIX} \ -DCMAKE_INSTALL_PREFIX=${PREFIX} \ -DWALBERLA_BUILD_WITH_PYTHON=ON \ - -DWALBERLA_BUILD_WITH_PYTHON_MODULE=ON \ - -DWALBERLA_BUILD_WITH_PYTHON_LBM=OFF \ .. make -j ${CPU_COUNT} pythonModuleInstall -- GitLab