diff --git a/.clang-format b/.clang-format index e0c6ea0..92520d0 100644 --- a/.clang-format +++ b/.clang-format @@ -52,7 +52,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeComma BreakStringLiterals: true -ColumnLimit: 82 +ColumnLimit: 120 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true Cpp11BracedListStyle: true diff --git a/.clang-tidy b/.clang-tidy index 93834af..8432375 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,41 +4,61 @@ Checks: > *-, - -bugprone-*, - -bugprone-macro-parentheses, + bugprone-*, + -bugprone-branch-clone, -bugprone-easily-swappable-parameters, + -bugprone-exception-escape, + -bugprone-macro-parentheses, -bugprone-narrowing-conversions, + -bugprone-signed-char-misuse, - -cert-*, + cert-*, + -cert-dcl03-c, + -cert-err33-c, -cert-err58-cpp, -cert-oop54-cpp, + -cert-str34-c, - -clang-analyzer-*, - -clang-diagnostics-*, + clang-analyzer-*, + clang-diagnostics-*, - -concurrency-*, + concurrency-*, + -concurrency-mt-unsafe, - -cppcoreguidelines-*, + cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-owning-memory, - -hicpp-*, - - -misc-*, + hicpp-*, + -hicpp-no-array-decay, + -hicpp-signed-bitwise, - -modernize-*, + misc-*, + -misc-confusable-identifiers, + -misc-include-cleaner, + -misc-use-anonymous-namespace, + modernize-*, performance-*, - -readability-*, - -readability-identifier-naming, - -readability-const-return-type, + readability-*, + -readability-function-cognitive-complexity, -readability-identifier-length, + -readability-identifier-naming, -readability-magic-numbers, + -*-avoid-c-arrays, + -*-no-malloc, -*-non-private-member-variables-in-classes, -*-special-member-functions, + -*-static-assert, + -*-vararg, WarningsAsErrors: "*" FormatStyle: none diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml new file mode 100644 index 0000000..0429466 --- /dev/null +++ b/.github/workflows/reuse.yml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch + +name: REUSE Compliance Check + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + lfs: true + + - name: REUSE Compliance Check + uses: fsfe/reuse-action@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77bb60b..c00ab82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,12 +7,32 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: + - id: check-case-conflict + - id: check-docstring-first - id: check-json - - id: check-yaml + - id: check-shebang-scripts-are-executable - id: check-toml + - id: check-yaml - id: check-xml - - id: trailing-whitespace - - id: end-of-file-fixer - id: detect-private-key - - id: check-case-conflict - # - id: check-added-large-files + - id: double-quote-string-fixer + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: mixed-line-ending + - id: trailing-whitespace + + - repo: https://github.com/hhatto/autopep8 + rev: v2.3.1 + hooks: + - id: autopep8 + + - repo: https://github.com/fsfe/reuse-tool + rev: v4.0.3 + hooks: + - id: reuse + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v18.1.8 + hooks: + - id: clang-format + types_or: [c, c++, cuda, metal] diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4db77..8db7149 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,16 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2024 Tobias Hienzsch - cmake_minimum_required(VERSION 3.24) -project(pffdtd VERSION 1.0.0 LANGUAGES C CXX) + +option(PFFDTD_ENABLE_CUDA "Build with CUDA" OFF) +option(PFFDTD_ENABLE_SYCL_ACPP "Build with AdaptiveCpp SYCL" OFF) +option(PFFDTD_ENABLE_SYCL_ONEAPI "Build with Intel SYCL" OFF) + +find_program(CCACHE ccache) +if (CCACHE) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) +endif () set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -12,8 +20,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -option(PFFDTD_ENABLE_ACPP_SYCL "Build with AdaptiveCpp SYCL" OFF) -option(PFFDTD_ENABLE_INTEL_SYCL "Build with Intel SYCL" OFF) +if(PFFDTD_ENABLE_CUDA) + enable_language(CUDA) + set(CMAKE_CUDA_STANDARD 20) +endif() + +project(pffdtd VERSION 0.1.0 LANGUAGES C CXX) include(FetchContent) FetchContent_Declare(mdspan GIT_REPOSITORY "https://github.com/kokkos/mdspan" GIT_TAG "stable" GIT_SHALLOW TRUE) @@ -24,12 +36,14 @@ find_package(fmt REQUIRED) find_package(HDF5 REQUIRED) find_package(OpenMP REQUIRED) -if(PFFDTD_ENABLE_ACPP_SYCL) +if(PFFDTD_ENABLE_SYCL_ACPP) find_package(AdaptiveCpp REQUIRED) + set(PFFDTD_HAS_SYCL TRUE) endif() -if(PFFDTD_ENABLE_INTEL_SYCL) +if(PFFDTD_ENABLE_SYCL_ONEAPI) find_package(IntelSYCL REQUIRED) + set(PFFDTD_HAS_SYCL TRUE) endif() add_subdirectory(src/cpp) diff --git a/conanfile.py b/conanfile.py index 6434b9a..15d0cb9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -5,15 +5,15 @@ class PFFDTD(ConanFile): - settings = "os", "compiler", "build_type", "arch" - generators = "CMakeToolchain", "CMakeDeps" + settings = 'os', 'compiler', 'build_type', 'arch' + generators = 'CMakeToolchain', 'CMakeDeps' def requirements(self): - self.requires("cli11/2.4.2") - self.requires("fmt/11.0.2") + self.requires('cli11/2.4.2') + self.requires('fmt/11.0.2') - if self.settings.os != "Macos": - self.requires("hdf5/1.14.4.3") + if self.settings.os != 'Macos': + self.requires('hdf5/1.14.4.3') def config_options(self): pass diff --git a/data/models/CTK_Church/CTK_Church.py b/data/models/CTK_Church/CTK_Church.py new file mode 100644 index 0000000..d7f4963 --- /dev/null +++ b/data/models/CTK_Church/CTK_Church.py @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Brian Hamilton +"""This shows a simple setup with Cartesian scheme, for a single-precision GPU run (<2GB VRAM) +""" + +from pffdtd.sim3d.setup import Setup3D + + +class CTK_Church(Setup3D): + """Christ the King (CTK) Church + """ + model_file = 'model_export.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'AcousticPanel': 'ctk_acoustic_panel.h5', + 'Altar': 'ctk_altar.h5', + 'Carpet': 'ctk_carpet.h5', + 'Ceiling': 'ctk_ceiling.h5', + 'Glass': 'ctk_window.h5', + 'PlushChair': 'ctk_chair.h5', + 'Tile': 'ctk_tile.h5', + 'Walls': 'ctk_walls.h5', + } + duration = 3.0 + Tc = 20 + rh = 50 + fcc = False + ppw = 10.5 + fmax = 800.0 + save_folder = '../../sim_data/CTK_Church/cpu' + save_folder_gpu = '../../sim_data/CTK_Church/gpu' + compress = 0 + draw_vox = True + draw_backend = 'polyscope' diff --git a/data/models/CTK_Church/CTK_viz.py b/data/models/CTK_Church/CTK_Church_viz.py similarity index 100% rename from data/models/CTK_Church/CTK_viz.py rename to data/models/CTK_Church/CTK_Church_viz.py diff --git a/data/models/CTK_Church/CTK_gpu.py b/data/models/CTK_Church/CTK_gpu.py deleted file mode 100644 index c309e2d..0000000 --- a/data/models/CTK_Church/CTK_gpu.py +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2021 Brian Hamilton -"""This shows a simple setup with Cartesian scheme, for a single-precision GPU run (<2GB VRAM) -""" - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model_export.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'AcousticPanel': 'ctk_acoustic_panel.h5', - 'Altar': 'ctk_altar.h5', - 'Carpet': 'ctk_carpet.h5', - 'Ceiling': 'ctk_ceiling.h5', - 'Glass': 'ctk_window.h5', - 'PlushChair': 'ctk_chair.h5', - 'Tile': 'ctk_tile.h5', - 'Walls': 'ctk_walls.h5', - }, - duration=3.0, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, # for 1% phase velocity error at fmax - fmax=1400.0, - save_folder='../../sim_data/ctk_cart/cpu', - save_folder_gpu='../../sim_data/ctk_cart/gpu', - compress=0, - draw_vox=True, - draw_backend='polyscope', -) diff --git a/data/models/CTK_Church/model_export.json b/data/models/CTK_Church/model_export.json index f50c263..32e5ccb 100755 --- a/data/models/CTK_Church/model_export.json +++ b/data/models/CTK_Church/model_export.json @@ -79,4 +79,4 @@ "name":"" }], "export_datetime":"2021-07-25 08:25:11 +0100" -} +} diff --git a/data/models/InfiniteBaffle/InfiniteBaffle.py b/data/models/InfiniteBaffle/InfiniteBaffle.py new file mode 100644 index 0000000..52e524e --- /dev/null +++ b/data/models/InfiniteBaffle/InfiniteBaffle.py @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch +import json + +from pffdtd.sim3d.setup import Setup3D + + +class InfiniteBaffle(Setup3D): + """Point source on infinite baffle in an anechoic chamber + """ + fmax = 2000 + ppw = 10.5 + fcc = False + model_file = 'model.json' + mat_folder = '../../materials', + duration = 0.3 + source_index = 1 + source_signal = 'impulse' + Tc = 20 + rh = 50 + save_folder = '../../sim_data/InfiniteBaffle/cpu' + save_folder_gpu = '../../sim_data/InfiniteBaffle/gpu' + draw_vox = True + draw_backend = 'polyscope' + compress = 0 + rot_az_el = [0, 0] + bmax = [17.15, 2.0, 17.15] + bmin = [0, 0, 0] + + def generate_model(self, constants): + mul = 3.0 if self.fcc else 2.0 + offset = constants.h * mul + + width = self.bmax[0] + length = self.bmax[1] + height = self.bmax[2] + + model = { + 'mats_hash': { + '_RIGID': { + 'tris': [ + [0, 2, 1], + [0, 3, 2], + [1, 5, 4], + [1, 3, 5] + ], + 'pts': [ + [0.0, length, 0.0], + [width/2, length, 0.0], + [width/2, length, height], + [0.0, length, height], + [width, length, 0.0], + [width, length, height] + ], + 'color': [255, 255, 255], + 'sides': [0, 0, 0, 0] + } + }, + 'sources': [ + {'name': 'S1', 'xyz': [width/2, length-offset, height/2]}, + ], + 'receivers': [ + {'name': 'R1', 'xyz': [width/2, offset, height/2 - 0.75]}, + {'name': 'R2', 'xyz': [width/2, offset, height/2 - 0.25]}, + {'name': 'R3', 'xyz': [width/2, offset, height/2 + 0.00]}, + {'name': 'R4', 'xyz': [width/2, offset, height/2 + 0.25]}, + {'name': 'R5', 'xyz': [width/2, offset, height/2 + 0.75]}, + ] + } + + with open(self.model_file, 'w') as file: + json.dump(model, file) + print('', file=file) diff --git a/data/models/InfiniteBaffle/InfiniteBaffle_cpu.py b/data/models/InfiniteBaffle/InfiniteBaffle_cpu.py deleted file mode 100644 index 83b7015..0000000 --- a/data/models/InfiniteBaffle/InfiniteBaffle_cpu.py +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={}, - duration=0.3, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, - fmax=2000.0, - save_folder='../../sim_data/InfiniteBaffle/cpu', - save_folder_gpu='../../sim_data/InfiniteBaffle/gpu', - draw_vox=True, - draw_backend='polyscope', - compress=0, - rot_az_el=[0, 0], - bmax=[5.0, 2.0, 5.0], - bmin=[0, 0, 0], -) diff --git a/data/models/InfiniteBaffle/InfiniteBaffle_model.py b/data/models/InfiniteBaffle/InfiniteBaffle_model.py deleted file mode 100644 index 316f3cf..0000000 --- a/data/models/InfiniteBaffle/InfiniteBaffle_model.py +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -import json - -from pffdtd.sim3d.constants import SimConstants - - -def main(): - fcc = False - fmax = 2000 - ppw = 10.5 - constants = SimConstants(20, 50, fmax=fmax, PPW=ppw, fcc=fcc) - mul = 3.0 if fcc else 2.0 - offset = constants.h * mul - - width = 5.0 - length = 2.0 - height = 5.0 - - model = { - "mats_hash": { - "_RIGID": { - "tris": [ - [0, 2, 1], - [0, 3, 2], - [1, 5, 4], - [1, 3, 5] - ], - "pts": [ - [0.0, length, 0.0], - [width/2, length, 0.0], - [width/2, length, height], - [0.0, length, height], - [width, length, 0.0], - [width, length, height] - ], - "color": [255, 255, 255], - "sides": [0, 0, 0, 0] - } - }, - "sources": [ - {"name": "S1", "xyz": [width/2, length-offset, height/2]}, - ], - "receivers": [ - {"name": "R1", "xyz": [width/2, offset, height/2 - 0.75]}, - {"name": "R2", "xyz": [width/2, offset, height/2 - 0.25]}, - {"name": "R3", "xyz": [width/2, offset, height/2 + 0.00]}, - {"name": "R4", "xyz": [width/2, offset, height/2 + 0.25]}, - {"name": "R5", "xyz": [width/2, offset, height/2 + 0.75]}, - ] - } - - with open("model.json", "w") as file: - json.dump(model, file) - - -main() diff --git a/data/models/InfiniteBaffle/model.json b/data/models/InfiniteBaffle/model.json index 156718e..f9018d4 100644 --- a/data/models/InfiniteBaffle/model.json +++ b/data/models/InfiniteBaffle/model.json @@ -1 +1 @@ -{"mats_hash": {"_RIGID": {"tris": [[0, 2, 1], [0, 3, 2], [1, 5, 4], [1, 3, 5]], "pts": [[0.0, 2.0, 0.0], [2.5, 2.0, 0.0], [2.5, 2.0, 5.0], [0.0, 2.0, 5.0], [5.0, 2.0, 0.0], [5.0, 2.0, 5.0]], "color": [255, 255, 255], "sides": [0, 0, 0, 0]}}, "sources": [{"name": "S1", "xyz": [2.5, 1.9673142857142858, 2.5]}], "receivers": [{"name": "R1", "xyz": [2.5, 0.03268571428571428, 1.75]}, {"name": "R2", "xyz": [2.5, 0.03268571428571428, 2.25]}, {"name": "R3", "xyz": [2.5, 0.03268571428571428, 2.5]}, {"name": "R4", "xyz": [2.5, 0.03268571428571428, 2.75]}, {"name": "R5", "xyz": [2.5, 0.03268571428571428, 3.25]}]} \ No newline at end of file +{"mats_hash": {"_RIGID": {"tris": [[0, 2, 1], [0, 3, 2], [1, 5, 4], [1, 3, 5]], "pts": [[0.0, 2.0, 0.0], [8.575, 2.0, 0.0], [8.575, 2.0, 17.15], [0.0, 2.0, 17.15], [17.15, 2.0, 0.0], [17.15, 2.0, 17.15]], "color": [255, 255, 255], "sides": [0, 0, 0, 0]}}, "sources": [{"name": "S1", "xyz": [8.575, 1.9673142857142858, 8.575]}], "receivers": [{"name": "R1", "xyz": [8.575, 0.03268571428571428, 7.824999999999999]}, {"name": "R2", "xyz": [8.575, 0.03268571428571428, 8.325]}, {"name": "R3", "xyz": [8.575, 0.03268571428571428, 8.575]}, {"name": "R4", "xyz": [8.575, 0.03268571428571428, 8.825]}, {"name": "R5", "xyz": [8.575, 0.03268571428571428, 9.325]}]} diff --git a/data/models/LivingRoom/LivingRoom.py b/data/models/LivingRoom/LivingRoom.py new file mode 100644 index 0000000..d6ac8c3 --- /dev/null +++ b/data/models/LivingRoom/LivingRoom.py @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch +import pathlib + +from pffdtd.sim3d.model_builder import MeshModelBuilder +from pffdtd.sim3d.setup import Setup3D + + +class LivingRoom(Setup3D): + model_file = 'model.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'Book Shelf': 'floor_wood.h5', + 'Ceiling': 'concrete_painted.h5', + 'Coffee Table': 'floor_wood.h5', + 'Couch': 'absorber_8000_100mm.h5', + 'Desk': 'floor_wood.h5', + 'Door': 'floor_wood.h5', + 'Floor': 'floor_wood.h5', + 'Kallax': 'floor_wood.h5', + 'Monitors': 'floor_wood.h5', + 'Speakers': 'floor_wood.h5', + 'Speaker Stands': 'door_iron.h5', + 'Table': 'floor_wood.h5', + 'TV 42': 'floor_wood.h5', + 'TV 55': 'floor_wood.h5', + 'TV Table': 'floor_wood.h5', + 'Walls': 'concrete_painted.h5', + 'Window': 'glas_thick.h5', + } + duration = 2.0 + Tc = 20 + rh = 50 + fcc = False + ppw = 10.5 + fmax = 800.0 + save_folder = '../../sim_data/LivingRoom/cpu' + save_folder_gpu = '../../sim_data/LivingRoom/gpu' + compress = 0 + draw_vox = True + draw_backend = 'polyscope' + + def generate_model(self, constants): + dir = pathlib.Path('.') + obj = dir/'obj' + + s1 = [3.65-0.5, 6.0-0.3, 1.2] + s2 = [3.65-0.5, 6.0-2.5, 1.2] + + r1 = [0.6, 6.0-1.3, 1.1] + r2 = [0.6, 6.0-2.0, 1.1] + + m = MeshModelBuilder() + m.add('Book Shelf', obj / 'book_shelf.obj', [200, 200, 200], reverse=True) + m.add('Ceiling', obj / 'ceiling.obj', [150, 150, 150], reverse=True) + m.add('Coffee Table', obj / 'coffee_table.obj', [10, 10, 10], reverse=True) + m.add('Couch', obj / 'couch.obj', [29, 50, 112], reverse=True) + m.add('Desk', obj / 'desk.obj', [103, 70, 55], reverse=True) + m.add('Door', obj / 'door.obj', [103, 70, 55], reverse=True) + m.add('Floor', obj / 'floor.obj', [133, 94, 66], reverse=True) + m.add('Kallax', obj / 'kallax.obj', [200, 200, 200], reverse=True) + m.add('Monitors', obj / 'monitors.obj', [15, 15, 15], reverse=True) + m.add('Speakers', obj / 'speakers.obj', [25, 25, 25], reverse=True) + m.add('Speaker Stands', obj / 'speaker_stands.obj', [5, 5, 5], reverse=True) + m.add('Table', obj / 'table.obj', [200, 200, 200], reverse=True) + m.add('TV 42', obj / 'tv_42.obj', [10, 10, 10], reverse=True) + m.add('TV 55', obj / 'tv_55.obj', [10, 10, 10], reverse=True) + m.add('TV Table', obj / 'tv_table.obj', [120, 120, 120], reverse=True) + m.add('Walls', obj / 'walls.obj', [175, 175, 175], reverse=True) + m.add('Window', obj / 'window.obj', [137, 207, 240], reverse=True) + m.add_source('S1', s1) + m.add_source('S2', s2) + m.add_receiver('R1', r1) + m.add_receiver('R2', r2) + m.write(self.model_file) diff --git a/data/models/LivingRoom/LivingRoom_cpu.py b/data/models/LivingRoom/LivingRoom_cpu.py deleted file mode 100644 index 9fa557f..0000000 --- a/data/models/LivingRoom/LivingRoom_cpu.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'Book Shelf': 'floor_wood.h5', - 'Ceiling': 'concrete_painted.h5', - 'Coffee Table': 'floor_wood.h5', - 'Couch': 'absorber_8000_100mm.h5', - 'Desk': 'floor_wood.h5', - 'Door': 'floor_wood.h5', - 'Floor': 'floor_wood.h5', - 'Kallax': 'floor_wood.h5', - 'Monitors': 'floor_wood.h5', - 'Speakers': 'floor_wood.h5', - 'Speaker Stands': 'door_iron.h5', - 'Table': 'floor_wood.h5', - 'TV 42': 'floor_wood.h5', - 'TV 55': 'floor_wood.h5', - 'TV Table': 'floor_wood.h5', - 'Walls': 'concrete_painted.h5', - 'Window': 'glas_thick.h5', - }, - duration=2.0, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, - fmax=800.0, - save_folder='../../sim_data/LivingRoom/cpu', - save_folder_gpu='../../sim_data/LivingRoom/gpu', - compress=0, - draw_vox=True, - draw_backend='polyscope', -) diff --git a/data/models/LivingRoom/LivingRoom_model.py b/data/models/LivingRoom/LivingRoom_model.py deleted file mode 100644 index 60278b1..0000000 --- a/data/models/LivingRoom/LivingRoom_model.py +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -import pathlib - -from pffdtd.sim3d.model_builder import MeshModelBuilder - - -def main(): - dir = pathlib.Path(".") - obj = dir/"obj" - - s1 = [3.65-0.5, 6.0-0.3, 1.2] - s2 = [3.65-0.5, 6.0-2.5, 1.2] - - r1 = [0.6, 6.0-1.3, 1.1] - r2 = [0.6, 6.0-2.0, 1.1] - - m = MeshModelBuilder() - m.add("Book Shelf", obj / 'book_shelf.obj', [200, 200, 200], reverse=True) - m.add("Ceiling", obj / 'ceiling.obj', [150, 150, 150], reverse=True) - m.add("Coffee Table", obj / 'coffee_table.obj', [10, 10, 10], reverse=True) - m.add("Couch", obj / 'couch.obj', [29, 50, 112], reverse=True) - m.add("Desk", obj / 'desk.obj', [103, 70, 55], reverse=True) - m.add("Door", obj / 'door.obj', [103, 70, 55], reverse=True) - m.add("Floor", obj / 'floor.obj', [133, 94, 66], reverse=True) - m.add("Kallax", obj / 'kallax.obj', [200, 200, 200], reverse=True) - m.add("Monitors", obj / 'monitors.obj', [15, 15, 15], reverse=True) - m.add("Speakers", obj / 'speakers.obj', [25, 25, 25], reverse=True) - m.add("Speaker Stands", obj / 'speaker_stands.obj', [5, 5, 5], reverse=True) - m.add("Table", obj / 'table.obj', [200, 200, 200], reverse=True) - m.add("TV 42", obj / 'tv_42.obj', [10, 10, 10], reverse=True) - m.add("TV 55", obj / 'tv_55.obj', [10, 10, 10], reverse=True) - m.add("TV Table", obj / 'tv_table.obj', [120, 120, 120], reverse=True) - m.add("Walls", obj / 'walls.obj', [175, 175, 175], reverse=True) - m.add("Window", obj / 'window.obj', [137, 207, 240], reverse=True) - m.add_source("S1", s1) - m.add_source("S2", s2) - m.add_receiver("R1", r1) - m.add_receiver("R2", r2) - m.write(dir / "model.json") - - -main() diff --git a/data/models/LivingRoom/LivingRoom_viz.py b/data/models/LivingRoom/LivingRoom_viz.py index 995f9a2..ccba273 100644 --- a/data/models/LivingRoom/LivingRoom_viz.py +++ b/data/models/LivingRoom/LivingRoom_viz.py @@ -10,24 +10,34 @@ insig_type='dhann30', # for viz diff_source=False, mat_files_dict={ + 'Book Shelf': 'floor_wood.h5', 'Ceiling': 'concrete_painted.h5', + 'Coffee Table': 'floor_wood.h5', + 'Couch': 'absorber_8000_100mm.h5', + 'Desk': 'floor_wood.h5', + 'Door': 'floor_wood.h5', 'Floor': 'floor_wood.h5', - 'Table': 'mv_wood.h5', - 'TV_Stand': 'mv_wood.h5', - 'Sofa': 'absorber_8000_100mm.h5', - 'Walls': 'concrete_painted.h5' + 'Kallax': 'floor_wood.h5', + 'Monitors': 'floor_wood.h5', + 'Speakers': 'floor_wood.h5', + 'Speaker Stands': 'door_iron.h5', + 'Table': 'floor_wood.h5', + 'TV 42': 'floor_wood.h5', + 'TV 55': 'floor_wood.h5', + 'TV Table': 'floor_wood.h5', + 'Walls': 'concrete_painted.h5', + 'Window': 'glas_thick.h5', }, duration=0.1, Tc=20, rh=50, - fcc_flag=True, - PPW=7.7, # for 2% phase velocity error at fmax - fmax=500.0, - save_folder='../../sim_data/LivingRoom/viz', # can run python from here + fcc_flag=False, + PPW=10.5, + fmax=800.0, + save_folder='../../sim_data/LivingRoom/viz', compress=0, draw_vox=True, - # will draw 'voxelization' (spheres are active boundary nodes, cubes rigid boundary nodes) - draw_backend='mayavi', + draw_backend='polyscope', ) # then run with python and 3D visualization: diff --git a/data/models/Localization/Localization.py b/data/models/Localization/Localization.py new file mode 100644 index 0000000..986340b --- /dev/null +++ b/data/models/Localization/Localization.py @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch +import numpy as np + +from pffdtd.sim3d.model_builder import RoomModelBuilder +from pffdtd.sim3d.setup import Setup3D + + +class Localization(Setup3D): + model_file = 'model.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'Ceiling': 'sabine_9512.h5', + 'Floor': 'sabine_9512.h5', + 'Walls': 'sabine_9512.h5', + } + duration = 0.4 + Tc = 20 + rh = 50 + fcc_flag = False + ppw = 10.5 + fmax = 800.0 + save_folder = '../../sim_data/Localization/cpu' + save_folder_gpu = '../../sim_data/Localization/gpu' + compress = 0 + draw_vox = True + draw_backend = 'polyscope' + + def generate_model(self, constants): + L = 3.0 + W = 3.0 + H = 3.0 + + source = [W/2, L-0.1, H/2] + mics = [ + np.array([0, 0, 0]), + np.array([1, 0, 0]), + np.array([0.5, np.sqrt(3)/2, 0]), + np.array([0.5, np.sqrt(3)/6, np.sqrt(6)/3]), + ] + + room = RoomModelBuilder(L, W, H) + room.with_colors({ + 'Ceiling': [200, 200, 200], + 'Floor': [151, 134, 122], + 'Walls': [255, 255, 255], + }) + + room.add_source('S1', source) + room.add_receiver('R1', list(mics[1-1]/2+[0.5, 0.5, 0.5])) + room.add_receiver('R2', list(mics[2-1]/2+[0.5, 0.5, 0.5])) + room.add_receiver('R3', list(mics[3-1]/2+[0.5, 0.5, 0.5])) + room.add_receiver('R4', list(mics[4-1]/2+[0.5, 0.5, 0.5])) + room.build(self.model_file) diff --git a/data/models/Localization/Localization_cpu.py b/data/models/Localization/Localization_cpu.py deleted file mode 100644 index 2ae8754..0000000 --- a/data/models/Localization/Localization_cpu.py +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'Ceiling': 'sabine_9512.h5', - 'Floor': 'sabine_9512.h5', - 'Walls': 'sabine_9512.h5', - }, - duration=0.4, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, - fmax=800.0, - save_folder='../../sim_data/Localization/cpu', - save_folder_gpu='../../sim_data/Localization/gpu', - compress=0, -) diff --git a/data/models/Localization/Localization_model.py b/data/models/Localization/Localization_model.py deleted file mode 100644 index b764a4f..0000000 --- a/data/models/Localization/Localization_model.py +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -import numpy as np - -from pffdtd.sim3d.model_builder import RoomModelBuilder - -L = 3.0 -W = 3.0 -H = 3.0 - -source = [W/2, L-0.1, H/2] -mics = [ - np.array([0, 0, 0]), - np.array([1, 0, 0]), - np.array([0.5, np.sqrt(3)/2, 0]), - np.array([0.5, np.sqrt(3)/6, np.sqrt(6)/3]), -] - -room = RoomModelBuilder(L, W, H) -room.with_colors({ - "Ceiling": [200, 200, 200], - "Floor": [151, 134, 122], - "Walls": [255, 255, 255], -}) - -room.add_source("S1", source) -room.add_receiver("R1", list(mics[1-1]/2+[0.5, 0.5, 0.5])) -room.add_receiver("R2", list(mics[2-1]/2+[0.5, 0.5, 0.5])) -room.add_receiver("R3", list(mics[3-1]/2+[0.5, 0.5, 0.5])) -room.add_receiver("R4", list(mics[4-1]/2+[0.5, 0.5, 0.5])) -room.build('model.json') diff --git a/data/models/Modes/Modes.py b/data/models/Modes/Modes.py new file mode 100644 index 0000000..0bc77af --- /dev/null +++ b/data/models/Modes/Modes.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch +from pffdtd.sim3d.model_builder import RoomModelBuilder +from pffdtd.sim3d.setup import Setup3D + + +class Modes(Setup3D): + model_file = 'model.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'Ceiling': 'sabine_03.h5', + 'Floor': 'sabine_03.h5', + 'Walls': 'sabine_03.h5', + } + duration = 3.75 + Tc = 20 + rh = 50 + fcc = False + ppw = 10.5 + fmax = 800.0 + save_folder = '../../sim_data/Modes/cpu' + save_folder_gpu = '../../sim_data/Modes/gpu' + compress = 0 + draw_vox = False + draw_backend = 'polyscope' + rot_az_el = [0, 0] + + def generate_model(self, constants): + width = 2.0 + length = 3.0 + height = 4.0 + + mul = 3.5 if self.fcc else 2.0 + offset = constants.h * mul + + room = RoomModelBuilder(length, width, height) + room.with_colors({ + 'Ceiling': [200, 200, 200], + 'Floor': [151, 134, 122], + 'Walls': [255, 255, 255], + }) + room.add_source('S1', [offset, offset, offset]) + room.add_receiver('R1', [width-offset, length-offset, height-offset]) + room.build(self.model_file) diff --git a/data/models/Modes/Modes_cpu.py b/data/models/Modes/Modes_cpu.py deleted file mode 100644 index d16d88c..0000000 --- a/data/models/Modes/Modes_cpu.py +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'Ceiling': 'sabine_03.h5', - 'Floor': 'sabine_03.h5', - 'Walls': 'sabine_03.h5', - }, - duration=3.75, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, - fmax=800.0, - save_folder='../../sim_data/Modes/cpu', - save_folder_gpu='../../sim_data/Modes/gpu', - compress=0, - draw_vox=False, - draw_backend='polyscope', - rot_az_el=[0, 0], -) diff --git a/data/models/Modes/Modes_model.py b/data/models/Modes/Modes_model.py deleted file mode 100644 index 090facf..0000000 --- a/data/models/Modes/Modes_model.py +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.constants import SimConstants -from pffdtd.sim3d.model_builder import RoomModelBuilder - -S = 0.4 -L = 7.00*S -W = 5.19*S -H = 3.70*S - -W = 2.0 -L = 3.0 -H = 4.0 - -Tc = 20 -rh = 50 -fcc = False -fmax = 800 -ppw = 10.5 -constants = SimConstants(Tc=Tc, rh=rh, fmax=fmax, - PPW=ppw, fcc=fcc, verbose=False) -mul = 3.5 if fcc else 2.0 -offset = constants.h * mul - -room = RoomModelBuilder(L, W, H) -room.with_colors({ - "Ceiling": [200, 200, 200], - "Floor": [151, 134, 122], - "Walls": [255, 255, 255], -}) - - -room.add_source("S1", [offset, offset, offset]) -room.add_receiver("R1", [W-offset, L-offset, H-offset]) - -model_file = 'model.json' -room.build(model_file) diff --git a/data/models/Musikverein_ConcertHall/MV_gpu.py b/data/models/Musikverein_ConcertHall/MV_gpu.py deleted file mode 100644 index fa6f124..0000000 --- a/data/models/Musikverein_ConcertHall/MV_gpu.py +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2021 Brian Hamilton -"""This shows a simple setup with FCC scheme, for a larger single-precision GPU run (<12GB VRAM) -""" -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model_export.json', - draw_backend='mayavi', - mat_folder='../../materials', - source_num=3, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'Floor': 'mv_floor.h5', - 'Chairs': 'mv_chairs.h5', - 'Plasterboard': 'mv_plasterboard.h5', - 'Window': 'mv_window.h5', - 'Wood': 'mv_wood.h5', - }, - duration=3.0, - Tc=20, - rh=50, - fcc_flag=True, - PPW=7.7, # for 1% phase velocity error at fmax - fmax=2500.0, - save_folder='../../sim_data/mv_fcc/gpu', - save_folder_gpu='../../sim_data/mv_fcc/gpu', - compress=3, # apply level-3 GZIP compression to larger h5 files -) diff --git a/data/models/Musikverein_ConcertHall/Musikverein_ConcertHall.py b/data/models/Musikverein_ConcertHall/Musikverein_ConcertHall.py new file mode 100644 index 0000000..a54c21a --- /dev/null +++ b/data/models/Musikverein_ConcertHall/Musikverein_ConcertHall.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Brian Hamilton +"""This shows a simple setup with FCC scheme, for a larger single-precision GPU run (<12GB VRAM) +""" +from pffdtd.sim3d.setup import Setup3D + + +class Musikverein_ConcertHall(Setup3D): + model_file = 'model_export.json' + mat_folder = '../../materials' + source_index = 3 + source_signal = 'impulse' + diff_source = True + materials = { + 'Floor': 'mv_floor.h5', + 'Chairs': 'mv_chairs.h5', + 'Plasterboard': 'mv_plasterboard.h5', + 'Window': 'mv_window.h5', + 'Wood': 'mv_wood.h5', + } + duration = 2.0 + Tc = 20 + rh = 50 + fcc = True + ppw = 5.6 + fmax = 1000.0 + save_folder = '../../sim_data/Musikverein_ConcertHall/gpu' + save_folder_gpu = '../../sim_data/Musikverein_ConcertHall/gpu' + compress = 3 + draw_vox = True + draw_backend = 'polyscope' diff --git a/data/models/Musikverein_ConcertHall/MV_viz.py b/data/models/Musikverein_ConcertHall/Musikverein_ConcertHall_viz.py similarity index 100% rename from data/models/Musikverein_ConcertHall/MV_viz.py rename to data/models/Musikverein_ConcertHall/Musikverein_ConcertHall_viz.py diff --git a/data/models/Musikverein_ConcertHall/model_export.json b/data/models/Musikverein_ConcertHall/model_export.json index 1398ec9..48ab0ea 100755 --- a/data/models/Musikverein_ConcertHall/model_export.json +++ b/data/models/Musikverein_ConcertHall/model_export.json @@ -91,4 +91,4 @@ "name":" R16" }], "export_datetime":"2021-07-25 08:28:45 +0100" -} +} diff --git a/data/models/Musikverein_ConcertHall/sources.csv b/data/models/Musikverein_ConcertHall/sources.csv index e233f49..0480a8f 100755 --- a/data/models/Musikverein_ConcertHall/sources.csv +++ b/data/models/Musikverein_ConcertHall/sources.csv @@ -1,4 +1,4 @@ X,Y,Z,Name 36.5,8.5,2.5,S1 40,15,3.5,S2 -39,11,3,S3 +39,11,3,S3 diff --git a/data/models/ProStudio/ProStudio.py b/data/models/ProStudio/ProStudio.py new file mode 100644 index 0000000..8ed9b28 --- /dev/null +++ b/data/models/ProStudio/ProStudio.py @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch +import pathlib + +from pffdtd.geometry.math import find_third_vertex, point_along_line +from pffdtd.sim3d.model_builder import MeshModelBuilder +from pffdtd.sim3d.setup import Setup3D + + +class ProStudio(Setup3D): + model_file = 'model.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'ATC Left': 'floor_wood.h5', + 'ATC Right': 'floor_wood.h5', + 'Ceiling': 'absorber_8000_200mm_gap_200mm.h5', + 'Console': 'door_iron.h5', + 'Couch': 'leather_arm_chair.h5', + # 'Diffusor': 'floor_wood.h5', + 'Floor': 'floor_wood_on_concrete.h5', + 'Outboard': 'door_iron.h5', + 'Rack': 'floor_wood.h5', + 'Raised Floor': 'floor_wood.h5', + 'Walls Back': 'absorber_8000_200mm_gap_200mm.h5', + 'Walls Front': 'absorber_8000_200mm_gap_100mm.h5', + 'Walls Side': 'absorber_8000_50mm.h5', + 'Windows': 'glas_thick.h5', + } + duration = 1.2 + Tc = 20 + rh = 50 + fcc = True + ppw = 7.7 + fmax = 4000.0 + save_folder = '../../sim_data/ProStudio/cpu' + save_folder_gpu = '../../sim_data/ProStudio/gpu' + draw_vox = True + draw_backend = 'polyscope' + compress = 0 + rot_az_el = [0, 0] + + def generate_model(self, constants): + print('--PRO-STUDIO: Build model') + dir = pathlib.Path('.') + obj = dir/'obj' + + mul = 4.0 if self.fcc else 2.0 + offset = constants.h * mul + + s1 = [3.38789-3.288/2, 6.90-offset, 1.062+0.332] + sub1 = s1.copy() + sub1[2] = 0.0 + offset + + s2 = s1.copy() + s2[0] += 3.288 + sub2 = s2.copy() + sub2[2] = 0.0 + offset + + s3 = [2.25, 3.55, 0.4] + + r1 = list(find_third_vertex(s1, s2)[1]) + r1[1] += (1.0-0.0) + r1[2] = 1.2 + + r2 = r1.copy() + r2[1] -= 1.0 + + r3 = point_along_line(r2, r1, 0.20) + r4 = point_along_line(r2, r1, 0.40) + r5 = point_along_line(r2, r1, 0.60) + r6 = point_along_line(r2, r1, 0.80) + + # # Couch + # r2 = r1.copy() + # r2[1] = 1.2 + # r2[2] = 1.3 + + # r3 = with_x_offset(r2, -0.73*1.5) + # r4 = with_x_offset(r2, -0.73*0.5) + # r5 = with_x_offset(r2, +0.73*0.5) + # r6 = with_x_offset(r2, +0.73*1.5) + + m = MeshModelBuilder() + m.add('ATC Left', obj / 'atc_left.obj', [5, 5, 5], reverse=True) + m.add('ATC Right', obj / 'atc_right.obj', [5, 5, 5], reverse=True) + m.add('Ceiling', obj / 'ceiling.obj', [60, 60, 60]) + m.add('Console', obj / 'console.obj', [60, 60, 60], reverse=True) + m.add('Couch', obj / 'couch.obj', [5, 5, 48], reverse=True) + # m.add("Diffusor", obj / 'diffusor.obj', [53, 33, 0], reverse=True) + m.add('Floor', obj / 'floor.obj', [53, 33, 0]) + m.add('Outboard', obj / 'outboard.obj', [0, 0, 0], reverse=True) + m.add('Rack', obj / 'rack.obj', [25, 25, 25], reverse=True) + m.add('Raised Floor', obj / 'raised_floor.obj', [25, 25, 25], reverse=True) + m.add('Walls Back', obj / 'walls_back.obj', [100, 100, 100]) + m.add('Walls Front', obj / 'walls_front.obj', [100, 100, 100]) + m.add('Walls Side', obj / 'walls_side.obj', [180, 180, 180]) + m.add('Windows', obj / 'windows.obj', [137, 207, 240], reverse=True) + m.add_source('S1', s1) + m.add_source('S2', s2) + # m.add_source("S3", s3) + # m.add_source("SUB1", sub1) + # m.add_source("SUB2", sub2) + m.add_receiver('R1', r1) + m.add_receiver('R2', r2) + m.add_receiver('R3', r3) + m.add_receiver('R4', r4) + m.add_receiver('R5', r5) + m.add_receiver('R6', r6) + m.write(self.model_file) diff --git a/data/models/ProStudio/ProStudio_cpu.py b/data/models/ProStudio/ProStudio_cpu.py deleted file mode 100644 index d897134..0000000 --- a/data/models/ProStudio/ProStudio_cpu.py +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'ATC Left': 'floor_wood.h5', - 'ATC Right': 'floor_wood.h5', - 'Ceiling': 'absorber_8000_200mm_gap_200mm.h5', - 'Console': 'door_iron.h5', - 'Couch': 'leather_arm_chair.h5', - # 'Diffusor': 'floor_wood.h5', - 'Floor': 'floor_wood_on_concrete.h5', - 'Outboard': 'door_iron.h5', - 'Rack': 'floor_wood.h5', - 'Raised Floor': 'floor_wood.h5', - 'Walls Back': 'absorber_8000_200mm_gap_200mm.h5', - 'Walls Front': 'absorber_8000_200mm_gap_100mm.h5', - 'Walls Side': 'absorber_8000_50mm.h5', - 'Windows': 'glas_thick.h5', - }, - duration=1.2, - Tc=20, - rh=50, - fcc_flag=True, - PPW=10.5, - fmax=800.0, - save_folder='../../sim_data/ProStudio/cpu', - # save_folder_gpu='../../sim_data/ProStudio/gpu', - draw_vox=True, - draw_backend='polyscope', - compress=0, - rot_az_el=[0, 0], -) diff --git a/data/models/ProStudio/ProStudio_model.py b/data/models/ProStudio/ProStudio_model.py deleted file mode 100644 index dff31f3..0000000 --- a/data/models/ProStudio/ProStudio_model.py +++ /dev/null @@ -1,82 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -import pathlib - -from pffdtd.geometry.math import find_third_vertex, point_along_line -from pffdtd.sim3d.constants import SimConstants -from pffdtd.sim3d.model_builder import MeshModelBuilder - - -def main(): - dir = pathlib.Path(".") - obj = dir/"obj" - - fcc = True - constants = SimConstants(Tc=20, rh=50, fmax=800, PPW=10.5, fcc=fcc) - mul = 4.0 if fcc else 2.0 - offset = constants.h * mul - - s1 = [3.38789-3.288/2, 6.90-offset, 1.062+0.332] - sub1 = s1.copy() - sub1[2] = 0.0 + offset - - s2 = s1.copy() - s2[0] += 3.288 - sub2 = s2.copy() - sub2[2] = 0.0 + offset - - s3 = [2.25, 3.55, 0.4] - - r1 = list(find_third_vertex(s1, s2)[1]) - r1[1] += (1.0-0.0) - r1[2] = 1.2 - - r2 = r1.copy() - r2[1] -= 1.0 - - r3 = point_along_line(r2, r1, 0.20) - r4 = point_along_line(r2, r1, 0.40) - r5 = point_along_line(r2, r1, 0.60) - r6 = point_along_line(r2, r1, 0.80) - - # # Couch - # r2 = r1.copy() - # r2[1] = 1.2 - # r2[2] = 1.3 - - # r3 = with_x_offset(r2, -0.73*1.5) - # r4 = with_x_offset(r2, -0.73*0.5) - # r5 = with_x_offset(r2, +0.73*0.5) - # r6 = with_x_offset(r2, +0.73*1.5) - - m = MeshModelBuilder() - m.add("ATC Left", obj / 'atc_left.obj', [5, 5, 5], reverse=True) - m.add("ATC Right", obj / 'atc_right.obj', [5, 5, 5], reverse=True) - m.add("Ceiling", obj / 'ceiling.obj', [60, 60, 60]) - m.add("Console", obj / 'console.obj', [60, 60, 60], reverse=True) - m.add("Couch", obj / 'couch.obj', [5, 5, 48], reverse=True) - # m.add("Diffusor", obj / 'diffusor.obj', [53, 33, 0], reverse=True) - m.add("Floor", obj / 'floor.obj', [53, 33, 0]) - m.add("Outboard", obj / 'outboard.obj', [0, 0, 0], reverse=True) - m.add("Rack", obj / 'rack.obj', [25, 25, 25], reverse=True) - m.add("Raised Floor", obj / 'raised_floor.obj', [25, 25, 25], reverse=True) - m.add("Walls Back", obj / 'walls_back.obj', [100, 100, 100]) - m.add("Walls Front", obj / 'walls_front.obj', [100, 100, 100]) - m.add("Walls Side", obj / 'walls_side.obj', [180, 180, 180]) - m.add("Windows", obj / 'windows.obj', [137, 207, 240], reverse=True) - m.add_source("S1", s1) - m.add_source("S2", s2) - # m.add_source("S3", s3) - # m.add_source("SUB1", sub1) - # m.add_source("SUB2", sub2) - m.add_receiver("R1", r1) - m.add_receiver("R2", r2) - m.add_receiver("R3", r3) - m.add_receiver("R4", r4) - m.add_receiver("R5", r5) - m.add_receiver("R6", r6) - m.write(dir / "model.json") - - -main() diff --git a/data/models/ProStudio/ProStudio_viz.py b/data/models/ProStudio/ProStudio_viz.py index 044bb4d..c9e7704 100644 --- a/data/models/ProStudio/ProStudio_viz.py +++ b/data/models/ProStudio/ProStudio_viz.py @@ -6,23 +6,35 @@ sim_setup_3d( model_json_file='model.json', mat_folder='../../materials', - source_num=1, + source_num=2, insig_type='dhann30', # for viz diff_source=False, mat_files_dict={ - 'Walls': 'concrete_painted.h5', + 'ATC Left': 'floor_wood.h5', + 'ATC Right': 'floor_wood.h5', + 'Ceiling': 'absorber_8000_200mm_gap_200mm.h5', + 'Console': 'door_iron.h5', + 'Couch': 'leather_arm_chair.h5', + # 'Diffusor': 'floor_wood.h5', + 'Floor': 'floor_wood_on_concrete.h5', + 'Outboard': 'door_iron.h5', + 'Rack': 'floor_wood.h5', + 'Raised Floor': 'floor_wood.h5', + 'Walls Back': 'absorber_8000_200mm_gap_200mm.h5', + 'Walls Front': 'absorber_8000_200mm_gap_100mm.h5', + 'Walls Side': 'absorber_8000_50mm.h5', + 'Windows': 'glas_thick.h5', }, - duration=0.1, # duration in seconds + duration=0.1, Tc=20, rh=50, fcc_flag=True, - PPW=7.7, # for 2% phase velocity error at fmax - fmax=500.0, - save_folder='../../sim_data/ProStudio/viz', # can run python from here + PPW=7.7, + fmax=800.0, + save_folder='../../sim_data/ProStudio/viz', compress=0, draw_vox=True, - # will draw 'voxelization' (spheres are active boundary nodes, cubes rigid boundary nodes) - draw_backend='mayavi', + draw_backend='polyscope', ) # then run with python and 3D visualization: diff --git a/data/models/ProStudio/model.json b/data/models/ProStudio/model.json index 52c226a..f40853b 100644 --- a/data/models/ProStudio/model.json +++ b/data/models/ProStudio/model.json @@ -1 +1 @@ -{"mats_hash": {"ATC Left": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 4, 0], [4, 6, 0], [3, 2, 5], [5, 2, 7]], "pts": [[1.305857422, 7.146186523, 1.062], [1.517857422, 6.7789916990000005, 1.062], [1.305857422, 7.146186523, 1.894], [1.517857422, 6.7789916990000005, 1.894], [2.148323975, 7.142991699, 1.062], [2.148323975, 7.142991699, 1.894], [1.9363238530000002, 7.510186523000001, 1.062], [1.9363238530000002, 7.510186523000001, 1.894]], "color": [5, 5, 5], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "ATC Right": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 6, 0], [4, 6, 1], [3, 2, 7], [5, 3, 7]], "pts": [[4.839456054999999, 7.510186523000001, 1.062], [4.627456055, 7.142991699, 1.062], [4.839456054999999, 7.510186523000001, 1.894], [4.627456055, 7.142991699, 1.894], [5.257922852, 6.7789916990000005, 1.062], [5.257922852, 6.7789916990000005, 1.894], [5.469922852, 7.146186523, 1.062], [5.469922852, 7.146186523, 1.894]], "color": [5, 5, 5], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Ceiling": {"tris": [[6, 2, 0], [1, 6, 0], [4, 3, 2], [6, 5, 4], [6, 4, 2]], "pts": [[5.790823242, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45], [6.775785645, 1.476, 4.435], [6.073909668000001, 0.473617249, 4.109021973], [3.387892822, -0.0, 3.955], [0.701875977, 0.473617249, 4.109021973], [-0.0, 1.476, 4.435]], "color": [60, 60, 60], "sides": [1, 1, 1, 1, 1]}, "Console": {"tris": [[1, 0, 13], [1, 13, 12], [2, 1, 12], [2, 12, 10], [2, 10, 14], [3, 2, 14], [4, 3, 14], [4, 14, 15], [5, 4, 15], [5, 15, 16], [6, 5, 16], [6, 16, 17], [7, 6, 17], [7, 17, 8], [9, 7, 8], [9, 8, 11], [0, 9, 11], [0, 11, 13], [12, 11, 10], [12, 13, 11], [11, 14, 10], [14, 8, 15], [11, 8, 14], [8, 16, 15], [8, 17, 16], [1, 2, 9], [1, 9, 0], [9, 2, 3], [3, 4, 7], [9, 3, 7], [7, 4, 5], [7, 5, 6]], "pts": [[5.203890137, 6.358, 1.025], [5.203890137, 6.518, 1.025], [5.203890137, 6.518, 0.476], [5.203890137, 5.753, 0.476], [5.203890137, 5.653, 0.647361084], [5.203890137, 5.283, 0.678], [5.203890137, 5.283, 0.736], [5.203890137, 5.653, 0.736], [1.571890015, 5.653, 0.736], [5.203890137, 6.263627441, 0.875], [1.571890015, 6.518, 0.476], [1.571890015, 6.263627441, 0.875], [1.571890015, 6.518, 1.025], [1.571890015, 6.358, 1.025], [1.571890015, 5.753, 0.476], [1.571890015, 5.653, 0.647361084], [1.571890015, 5.283, 0.678], [1.571890015, 5.283, 0.736]], "color": [60, 60, 60], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Couch": {"tris": [[1, 0, 12], [2, 0, 1], [2, 3, 0], [4, 3, 2], [4, 5, 3], [6, 5, 4], [6, 12, 5], [1, 12, 6], [0, 5, 12], [3, 5, 0], [2, 1, 6], [4, 2, 6], [9, 8, 7], [10, 8, 9], [10, 0, 8], [11, 0, 10], [11, 12, 0], [13, 12, 11], [13, 7, 12], [9, 7, 13], [8, 12, 7], [0, 12, 8], [10, 9, 13], [11, 10, 13], [14, 3, 5], [15, 3, 14], [15, 16, 3], [17, 16, 15], [17, 18, 16], [19, 18, 17], [19, 5, 18], [14, 5, 19], [3, 18, 5], [16, 18, 3], [15, 14, 19], [17, 15, 19], [21, 20, 1], [20, 22, 1], [20, 23, 22], [23, 24, 22], [23, 25, 24], [25, 26, 24], [25, 21, 26], [21, 1, 26], [22, 24, 1], [24, 26, 1], [20, 21, 23], [23, 21, 25], [25, 23, 26], [23, 24, 26], [23, 27, 24], [27, 28, 24], [27, 29, 28], [29, 30, 28], [29, 25, 30], [25, 26, 30], [24, 28, 26], [28, 30, 26], [23, 25, 27], [27, 25, 29], [29, 27, 30], [27, 28, 30], [27, 31, 28], [31, 32, 28], [31, 33, 32], [33, 34, 32], [33, 29, 34], [29, 30, 34], [28, 32, 30], [32, 34, 30], [27, 29, 31], [31, 29, 33], [33, 31, 34], [31, 32, 34], [31, 35, 32], [35, 36, 32], [35, 37, 36], [37, 6, 36], [37, 33, 6], [33, 34, 6], [32, 36, 34], [36, 6, 34], [31, 33, 35], [35, 33, 37]], "pts": [[1.927890015, 0.67, 0.328], [1.927890015, 1.65, 0.508], [1.927890015, 0.67, 0.508], [4.847890137, 0.67, 0.328], [4.847890137, 0.67, 0.508], [4.847890137, 1.65, 0.328], [4.847890137, 1.65, 0.508], [1.7778900149999999, 1.65, 0.328], [1.7778900149999999, 0.67, 0.328], [1.7778900149999999, 1.65, 1.008], [1.7778900149999999, 0.67, 1.008], [1.927890015, 0.67, 1.008], [1.927890015, 1.65, 0.328], [1.927890015, 1.65, 1.008], [4.847890137, 1.65, 1.008], [4.847890137, 0.67, 1.008], [4.997890137000001, 0.67, 0.328], [4.997890137000001, 0.67, 1.008], [4.997890137000001, 1.65, 0.328], [4.997890137000001, 1.65, 1.008], [1.927890015, 1.1, 0.748], [1.927890015, 1.65, 0.748], [1.927890015, 1.1, 0.508], [2.657889893, 1.1, 0.748], [2.657889893, 1.1, 0.508], [2.657889893, 1.65, 0.748], [2.657889893, 1.65, 0.508], [3.387889893, 1.1, 0.748], [3.387889893, 1.1, 0.508], [3.387889893, 1.65, 0.748], [3.387889893, 1.65, 0.508], [4.117890137000001, 1.1, 0.748], [4.117890137000001, 1.1, 0.508], [4.117890137000001, 1.65, 0.748], [4.117890137000001, 1.65, 0.508], [4.847890137, 1.1, 0.748], [4.847890137, 1.1, 0.508], [4.847890137, 1.65, 0.748]], "color": [5, 5, 48], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Floor": {"tris": [[2, 3, 4], [2, 5, 3], [3, 6, 7], [1, 8, 5], [0, 8, 1], [5, 8, 3], [3, 8, 6]], "pts": [[4.857248047, 7.601, 0.0], [1.9185378419999999, 7.601, 0.0], [-0.0, 1.476, 0.0], [3.387892822, -0.0, 0.0], [0.701875977, 0.473617249, 0.0], [0.9849625240000001, 7.062, 0.0], [6.775785645, 1.476, 0.0], [6.073909668000001, 0.473617249, 0.0], [5.790823242, 7.062, 0.0]], "color": [53, 33, 0], "sides": [1, 1, 1, 1, 1, 1, 1]}, "Outboard": {"tris": [[2, 1, 0], [3, 2, 0], [3, 0, 4], [5, 3, 4], [5, 4, 6], [7, 5, 6], [7, 6, 1], [2, 7, 1], [6, 0, 1], [6, 4, 0], [7, 2, 3], [7, 3, 5], [10, 9, 8], [11, 9, 10], [11, 12, 9], [13, 12, 11], [13, 14, 12], [15, 14, 13], [15, 10, 14], [10, 8, 14], [9, 12, 8], [12, 14, 8], [11, 10, 13], [13, 10, 15]], "pts": [[3.1381999510000003, 3.74, 0.719], [3.62, 3.74, 0.719], [3.62, 3.7375178219999996, 0.7187010500000001], [3.1381999510000003, 3.7375178219999996, 0.7187010500000001], [3.1381999510000003, 3.729597412, 0.805375854], [3.1381999510000003, 3.727115479, 0.805076904], [3.62, 3.729597412, 0.805375854], [3.62, 3.727115479, 0.805076904], [3.593600098, 3.509664307, 0.691259888], [3.593600098, 3.7375178219999996, 0.7187010500000001], [3.593600098, 3.499261963, 0.777635681], [3.593600098, 3.727115479, 0.805076904], [3.1646000979999998, 3.7375178219999996, 0.7187010500000001], [3.1646000979999998, 3.727115479, 0.805076904], [3.1646000979999998, 3.509664307, 0.691259888], [3.1646000979999998, 3.499261963, 0.777635681]], "color": [0, 0, 0], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Rack": {"tris": [[2, 1, 0], [0, 1, 3], [5, 4, 1], [2, 5, 1], [0, 3, 17], [54, 0, 17], [3, 7, 6], [3, 8, 7], [3, 9, 8], [3, 10, 9], [3, 11, 10], [3, 12, 11], [3, 13, 12], [3, 14, 13], [6, 15, 3], [8, 16, 7], [16, 17, 15], [15, 17, 3], [8, 18, 16], [16, 18, 17], [18, 19, 17], [21, 1, 20], [14, 1, 22], [24, 1, 23], [26, 1, 25], [3, 1, 14], [22, 1, 24], [23, 1, 21], [20, 1, 26], [9, 27, 19], [10, 27, 9], [19, 27, 17], [27, 28, 17], [1, 29, 25], [1, 30, 29], [11, 31, 28], [12, 31, 11], [28, 31, 17], [31, 32, 17], [13, 33, 32], [32, 33, 17], [14, 33, 13], [22, 35, 34], [24, 35, 22], [23, 37, 36], [21, 37, 23], [20, 39, 38], [26, 39, 20], [29, 40, 25], [29, 41, 40], [38, 4, 37], [41, 4, 40], [36, 4, 35], [40, 4, 39], [42, 4, 41], [30, 4, 42], [35, 4, 34], [1, 4, 30], [37, 4, 36], [39, 4, 38], [33, 4, 17], [34, 4, 33], [0, 44, 43], [0, 43, 45], [0, 45, 46], [0, 46, 47], [0, 47, 48], [0, 48, 49], [0, 49, 50], [0, 50, 51], [44, 0, 52], [45, 43, 53], [53, 52, 54], [52, 0, 54], [45, 53, 55], [53, 54, 55], [55, 54, 56], [51, 57, 2], [59, 58, 2], [61, 60, 2], [63, 62, 2], [0, 51, 2], [57, 59, 2], [58, 61, 2], [60, 63, 2], [46, 56, 64], [47, 46, 64], [56, 54, 64], [64, 54, 65], [2, 62, 66], [2, 66, 67], [48, 65, 68], [49, 48, 68], [65, 54, 68], [68, 54, 69], [69, 54, 70], [50, 69, 70], [51, 50, 70], [57, 72, 71], [59, 57, 71], [58, 74, 73], [61, 58, 73], [63, 60, 75], [63, 75, 76], [62, 78, 77], [66, 62, 77], [2, 67, 79], [73, 74, 5], [76, 75, 5], [77, 78, 5], [2, 79, 5], [74, 71, 5], [75, 73, 5], [78, 76, 5], [79, 77, 5], [71, 72, 5], [72, 54, 5], [70, 54, 72], [54, 17, 5], [5, 17, 4], [60, 61, 21], [60, 21, 20], [60, 20, 75], [75, 20, 38], [75, 38, 37], [73, 75, 37], [73, 37, 61], [61, 37, 21], [53, 7, 16], [43, 7, 53], [53, 15, 52], [53, 16, 15], [44, 15, 6], [52, 15, 44], [43, 6, 7], [44, 6, 43], [56, 9, 19], [46, 9, 56], [56, 18, 55], [56, 19, 18], [45, 18, 8], [55, 18, 45], [46, 8, 9], [45, 8, 46], [65, 11, 28], [48, 11, 65], [65, 27, 64], [65, 28, 27], [47, 27, 10], [64, 27, 47], [47, 11, 48], [47, 10, 11], [69, 50, 32], [50, 13, 32], [68, 32, 31], [69, 32, 68], [49, 31, 12], [68, 31, 49], [49, 13, 50], [49, 12, 13], [72, 57, 34], [57, 22, 34], [70, 72, 33], [72, 34, 33], [51, 70, 14], [70, 33, 14], [57, 14, 22], [51, 14, 57], [74, 58, 36], [58, 23, 36], [74, 35, 71], [74, 36, 35], [59, 71, 24], [71, 35, 24], [59, 23, 58], [59, 24, 23], [78, 62, 40], [62, 25, 40], [78, 39, 76], [78, 40, 39], [63, 76, 26], [76, 39, 26], [62, 26, 25], [63, 26, 62], [67, 66, 29], [67, 29, 30], [67, 30, 79], [79, 30, 42], [79, 42, 41], [77, 79, 41], [77, 41, 66], [66, 41, 29], [81, 80, 90], [80, 82, 90], [80, 161, 82], [83, 161, 80], [83, 84, 161], [84, 155, 161], [84, 90, 155], [81, 90, 84], [82, 155, 90], [161, 155, 82], [80, 81, 84], [83, 80, 84], [87, 85, 82], [86, 87, 82], [89, 88, 87], [87, 88, 85], [86, 82, 90], [91, 86, 90], [126, 93, 92], [125, 94, 126], [126, 94, 93], [125, 95, 94], [125, 96, 95], [88, 97, 85], [95, 97, 94], [125, 98, 96], [95, 99, 97], [97, 99, 85], [98, 100, 96], [99, 100, 85], [98, 101, 100], [98, 90, 102], [125, 90, 98], [102, 90, 103], [90, 104, 103], [103, 105, 102], [103, 106, 105], [104, 82, 107], [107, 82, 106], [90, 82, 104], [106, 82, 105], [100, 82, 85], [101, 82, 100], [105, 82, 101], [110, 109, 108], [217, 110, 111], [110, 108, 111], [217, 111, 112], [217, 112, 113], [89, 87, 114], [112, 111, 114], [217, 113, 115], [112, 114, 116], [114, 87, 116], [115, 113, 117], [116, 87, 117], [115, 117, 118], [115, 119, 91], [217, 115, 91], [119, 120, 91], [91, 120, 121], [120, 119, 122], [120, 122, 123], [124, 123, 86], [121, 124, 86], [91, 121, 86], [123, 122, 86], [118, 87, 86], [117, 87, 118], [122, 118, 86], [114, 97, 88], [89, 114, 88], [91, 90, 125], [217, 91, 125], [217, 125, 126], [110, 217, 126], [110, 126, 92], [109, 110, 92], [109, 92, 108], [108, 92, 93], [108, 93, 94], [111, 108, 94], [111, 94, 114], [114, 94, 97], [116, 117, 99], [117, 100, 99], [112, 99, 95], [116, 99, 112], [113, 95, 96], [112, 95, 113], [117, 113, 100], [113, 96, 100], [119, 102, 105], [122, 119, 105], [122, 105, 101], [118, 122, 101], [118, 101, 115], [115, 101, 98], [115, 98, 119], [119, 98, 102], [121, 104, 107], [124, 121, 107], [124, 107, 106], [123, 124, 106], [123, 106, 120], [120, 106, 103], [120, 103, 121], [121, 103, 104], [128, 127, 149], [161, 128, 149], [349, 129, 128], [128, 129, 127], [161, 149, 130], [155, 161, 130], [133, 132, 131], [135, 134, 133], [133, 134, 132], [135, 136, 134], [135, 137, 136], [129, 138, 127], [136, 138, 134], [135, 139, 137], [136, 140, 138], [138, 140, 127], [139, 141, 137], [140, 141, 127], [139, 142, 141], [139, 130, 143], [135, 130, 139], [143, 130, 144], [130, 145, 144], [144, 146, 143], [144, 147, 146], [145, 149, 148], [148, 149, 147], [130, 149, 145], [147, 149, 146], [141, 149, 127], [142, 149, 141], [146, 149, 142], [374, 150, 352], [373, 374, 151], [374, 352, 151], [373, 151, 353], [373, 353, 354], [349, 128, 355], [353, 151, 355], [373, 354, 152], [353, 355, 356], [355, 128, 356], [152, 354, 153], [356, 128, 153], [152, 153, 154], [152, 156, 155], [373, 152, 155], [156, 157, 155], [155, 157, 158], [157, 156, 159], [157, 159, 160], [162, 160, 161], [158, 162, 161], [155, 158, 161], [160, 159, 161], [154, 128, 161], [153, 128, 154], [159, 154, 161], [355, 138, 129], [349, 355, 129], [155, 130, 135], [373, 155, 135], [373, 135, 133], [374, 373, 133], [374, 133, 131], [150, 374, 131], [150, 131, 352], [352, 131, 132], [352, 132, 134], [151, 352, 134], [151, 134, 355], [355, 134, 138], [356, 153, 140], [153, 141, 140], [353, 140, 136], [356, 140, 353], [354, 136, 137], [353, 136, 354], [153, 354, 141], [354, 137, 141], [156, 143, 146], [159, 156, 146], [159, 146, 142], [154, 159, 142], [154, 142, 152], [152, 142, 139], [152, 139, 156], [156, 139, 143], [158, 145, 148], [162, 158, 148], [162, 148, 147], [160, 162, 147], [160, 147, 157], [157, 147, 144], [157, 144, 158], [158, 144, 145], [213, 164, 163], [213, 163, 165], [213, 165, 166], [213, 166, 167], [213, 167, 168], [213, 168, 169], [213, 169, 170], [213, 170, 171], [213, 171, 172], [213, 172, 173], [213, 173, 174], [213, 174, 175], [164, 213, 214], [164, 214, 176], [176, 214, 177], [165, 163, 177], [177, 214, 178], [165, 177, 178], [178, 214, 179], [167, 166, 179], [179, 214, 180], [167, 179, 180], [180, 214, 181], [169, 168, 181], [184, 183, 182], [186, 185, 182], [188, 187, 182], [175, 189, 182], [191, 190, 182], [192, 184, 182], [183, 186, 182], [185, 188, 182], [187, 191, 182], [190, 193, 182], [213, 175, 182], [189, 192, 182], [169, 181, 194], [181, 214, 194], [171, 170, 195], [171, 195, 196], [173, 172, 197], [173, 197, 198], [175, 174, 199], [175, 199, 200], [192, 189, 201], [192, 201, 202], [183, 184, 203], [184, 204, 203], [185, 186, 205], [185, 205, 206], [187, 188, 207], [187, 207, 208], [190, 191, 209], [190, 209, 210], [182, 193, 211], [207, 206, 216], [209, 208, 216], [182, 211, 216], [211, 210, 216], [208, 207, 216], [210, 209, 216], [194, 214, 216], [195, 194, 216], [196, 195, 216], [197, 196, 216], [198, 197, 216], [199, 198, 216], [200, 199, 216], [201, 200, 216], [202, 201, 216], [204, 202, 216], [203, 204, 216], [205, 203, 216], [206, 205, 216], [213, 212, 215], [213, 215, 214], [216, 215, 217], [216, 214, 215], [182, 135, 212], [182, 212, 213], [135, 182, 216], [135, 216, 217], [219, 218, 166], [218, 179, 166], [220, 179, 218], [220, 178, 179], [220, 221, 178], [221, 165, 178], [219, 165, 221], [219, 166, 165], [223, 222, 170], [222, 195, 170], [224, 195, 222], [224, 194, 195], [224, 225, 194], [225, 169, 194], [223, 169, 225], [223, 170, 169], [227, 226, 174], [226, 199, 174], [228, 199, 226], [228, 198, 199], [228, 229, 198], [229, 173, 198], [227, 173, 229], [227, 174, 173], [231, 230, 189], [230, 201, 189], [232, 201, 230], [232, 200, 201], [232, 233, 200], [233, 175, 200], [233, 189, 175], [231, 189, 233], [235, 234, 184], [234, 204, 184], [236, 204, 234], [236, 202, 204], [236, 237, 202], [237, 192, 202], [237, 184, 192], [235, 184, 237], [239, 238, 186], [238, 205, 186], [240, 205, 238], [240, 203, 205], [240, 241, 203], [241, 183, 203], [239, 183, 241], [239, 186, 183], [243, 242, 188], [242, 207, 188], [244, 207, 242], [244, 206, 207], [244, 245, 206], [245, 185, 206], [245, 188, 185], [243, 188, 245], [247, 246, 191], [246, 209, 191], [248, 209, 246], [248, 208, 209], [248, 249, 208], [249, 187, 208], [249, 191, 187], [247, 191, 249], [251, 250, 193], [250, 211, 193], [252, 211, 250], [252, 210, 211], [252, 190, 210], [253, 190, 252], [253, 193, 190], [251, 193, 253], [255, 254, 163], [254, 177, 163], [256, 177, 254], [256, 176, 177], [256, 257, 176], [257, 164, 176], [257, 163, 164], [255, 163, 257], [259, 258, 168], [258, 181, 168], [260, 181, 258], [260, 180, 181], [260, 261, 180], [261, 167, 180], [261, 168, 167], [259, 168, 261], [263, 262, 172], [262, 197, 172], [264, 197, 262], [264, 196, 197], [264, 265, 196], [265, 171, 196], [263, 171, 265], [263, 172, 171], [135, 251, 253], [135, 253, 247], [135, 247, 249], [135, 249, 243], [135, 243, 245], [135, 245, 239], [135, 239, 241], [135, 241, 235], [135, 235, 237], [135, 237, 231], [135, 231, 233], [135, 217, 250], [251, 135, 250], [250, 217, 252], [247, 253, 252], [252, 217, 246], [247, 252, 246], [246, 217, 248], [248, 217, 242], [249, 248, 242], [243, 249, 242], [242, 217, 244], [265, 223, 212], [225, 259, 212], [261, 219, 212], [221, 255, 212], [223, 225, 212], [219, 221, 212], [227, 229, 212], [263, 265, 212], [259, 261, 212], [255, 257, 212], [135, 233, 212], [233, 227, 212], [229, 263, 212], [245, 244, 238], [239, 245, 238], [241, 240, 234], [235, 241, 234], [231, 237, 236], [231, 236, 230], [233, 232, 226], [227, 233, 226], [229, 228, 262], [263, 229, 262], [223, 265, 222], [265, 264, 222], [259, 225, 258], [225, 224, 258], [219, 261, 218], [261, 260, 218], [255, 221, 254], [221, 220, 254], [212, 257, 256], [220, 218, 215], [260, 258, 215], [212, 256, 215], [258, 224, 215], [218, 260, 215], [256, 254, 215], [254, 220, 215], [264, 217, 215], [262, 217, 264], [228, 217, 262], [226, 217, 228], [232, 217, 226], [230, 217, 232], [236, 217, 230], [234, 217, 236], [240, 217, 234], [238, 217, 240], [244, 217, 238], [222, 264, 215], [224, 222, 215], [268, 267, 266], [266, 267, 269], [345, 270, 267], [268, 345, 267], [266, 269, 271], [319, 266, 271], [269, 273, 272], [269, 274, 273], [269, 275, 274], [269, 276, 275], [269, 277, 276], [269, 278, 277], [269, 279, 278], [269, 280, 279], [272, 281, 269], [274, 282, 273], [282, 271, 281], [281, 271, 269], [274, 283, 282], [282, 283, 271], [283, 284, 271], [286, 267, 285], [280, 267, 287], [289, 267, 288], [291, 267, 290], [269, 267, 280], [287, 267, 289], [288, 267, 286], [285, 267, 291], [275, 292, 284], [276, 292, 275], [284, 292, 271], [292, 293, 271], [267, 294, 290], [267, 295, 294], [277, 296, 293], [278, 296, 277], [293, 296, 271], [296, 297, 271], [279, 298, 297], [297, 298, 271], [280, 298, 279], [287, 300, 299], [289, 300, 287], [288, 302, 301], [286, 302, 288], [285, 304, 303], [291, 304, 285], [294, 305, 290], [294, 306, 305], [303, 270, 302], [306, 270, 305], [301, 270, 300], [305, 270, 304], [307, 270, 306], [295, 270, 307], [300, 270, 299], [267, 270, 295], [302, 270, 301], [304, 270, 303], [298, 270, 271], [299, 270, 298], [266, 309, 308], [266, 308, 310], [266, 310, 311], [266, 311, 312], [266, 312, 313], [266, 313, 314], [266, 314, 315], [266, 315, 316], [309, 266, 317], [310, 308, 318], [318, 317, 319], [317, 266, 319], [310, 318, 320], [318, 319, 320], [320, 319, 321], [316, 322, 268], [324, 323, 268], [326, 325, 268], [328, 327, 268], [266, 316, 268], [322, 324, 268], [323, 326, 268], [325, 328, 268], [311, 321, 329], [312, 311, 329], [321, 319, 329], [329, 319, 330], [268, 327, 331], [268, 331, 332], [313, 330, 333], [314, 313, 333], [330, 319, 333], [333, 319, 334], [334, 319, 335], [315, 334, 335], [316, 315, 335], [322, 337, 336], [324, 322, 336], [323, 339, 338], [326, 323, 338], [328, 325, 340], [328, 340, 341], [327, 343, 342], [331, 327, 342], [268, 332, 344], [338, 339, 345], [341, 340, 345], [342, 343, 345], [268, 344, 345], [339, 336, 345], [340, 338, 345], [343, 341, 345], [344, 342, 345], [336, 337, 345], [337, 319, 345], [335, 319, 337], [319, 271, 345], [345, 271, 270], [325, 326, 286], [325, 286, 285], [325, 285, 340], [340, 285, 303], [340, 303, 302], [338, 340, 302], [338, 302, 326], [326, 302, 286], [318, 273, 282], [308, 273, 318], [318, 281, 317], [318, 282, 281], [309, 281, 272], [317, 281, 309], [308, 272, 273], [309, 272, 308], [321, 275, 284], [311, 275, 321], [321, 283, 320], [321, 284, 283], [310, 283, 274], [320, 283, 310], [311, 274, 275], [310, 274, 311], [330, 277, 293], [313, 277, 330], [330, 292, 329], [330, 293, 292], [312, 292, 276], [329, 292, 312], [312, 277, 313], [312, 276, 277], [334, 315, 297], [315, 279, 297], [333, 297, 296], [334, 297, 333], [314, 296, 278], [333, 296, 314], [314, 279, 315], [314, 278, 279], [337, 322, 299], [322, 287, 299], [335, 337, 298], [337, 299, 298], [316, 335, 280], [335, 298, 280], [322, 280, 287], [316, 280, 322], [339, 323, 301], [323, 288, 301], [339, 300, 336], [339, 301, 300], [324, 336, 289], [336, 300, 289], [324, 288, 323], [324, 289, 288], [343, 327, 305], [327, 290, 305], [343, 304, 341], [343, 305, 304], [328, 341, 291], [341, 304, 291], [327, 291, 290], [328, 291, 327], [332, 331, 294], [332, 294, 295], [332, 295, 344], [344, 295, 307], [344, 307, 306], [342, 344, 306], [342, 306, 331], [331, 306, 294], [84, 83, 155], [83, 161, 155], [83, 409, 161], [346, 409, 83], [346, 347, 409], [347, 381, 409], [347, 155, 381], [84, 155, 347], [161, 381, 155], [409, 381, 161], [83, 84, 347], [346, 83, 347], [348, 128, 161], [371, 348, 161], [350, 349, 348], [348, 349, 128], [371, 161, 155], [351, 371, 155], [374, 352, 150], [373, 151, 374], [374, 151, 352], [373, 353, 151], [373, 354, 353], [349, 355, 128], [353, 355, 151], [373, 152, 354], [353, 356, 355], [355, 356, 128], [152, 153, 354], [356, 153, 128], [152, 154, 153], [152, 155, 156], [373, 155, 152], [156, 155, 157], [155, 158, 157], [157, 159, 156], [157, 160, 159], [158, 161, 162], [162, 161, 160], [155, 161, 158], [160, 161, 159], [153, 161, 128], [154, 161, 153], [159, 161, 154], [375, 376, 357], [464, 375, 358], [375, 357, 358], [464, 358, 359], [464, 359, 360], [350, 348, 361], [359, 358, 361], [464, 360, 362], [359, 361, 363], [361, 348, 363], [362, 360, 364], [363, 348, 364], [362, 364, 365], [362, 366, 351], [464, 362, 351], [366, 367, 351], [351, 367, 368], [367, 366, 369], [367, 369, 370], [372, 370, 371], [368, 372, 371], [351, 368, 371], [370, 369, 371], [365, 348, 371], [364, 348, 365], [369, 365, 371], [361, 355, 349], [350, 361, 349], [351, 155, 373], [464, 351, 373], [464, 373, 374], [375, 464, 374], [375, 374, 150], [376, 375, 150], [376, 150, 357], [357, 150, 352], [357, 352, 151], [358, 357, 151], [358, 151, 361], [361, 151, 355], [363, 364, 356], [364, 153, 356], [359, 356, 353], [363, 356, 359], [360, 353, 354], [359, 353, 360], [364, 360, 153], [360, 354, 153], [366, 156, 159], [369, 366, 159], [369, 159, 154], [365, 369, 154], [365, 154, 362], [362, 154, 152], [362, 152, 366], [366, 152, 156], [368, 158, 162], [372, 368, 162], [372, 162, 160], [370, 372, 160], [370, 160, 367], [367, 160, 157], [367, 157, 368], [368, 157, 158], [379, 378, 377], [409, 379, 377], [600, 380, 379], [379, 380, 378], [409, 377, 392], [381, 409, 392], [411, 382, 412], [515, 383, 411], [411, 383, 382], [515, 384, 383], [515, 385, 384], [380, 386, 378], [384, 386, 383], [515, 387, 385], [384, 388, 386], [386, 388, 378], [387, 389, 385], [388, 389, 378], [387, 390, 389], [387, 392, 391], [515, 392, 387], [391, 392, 393], [392, 394, 393], [393, 395, 391], [393, 396, 395], [394, 377, 397], [397, 377, 396], [392, 377, 394], [396, 377, 395], [389, 377, 378], [390, 377, 389], [395, 377, 390], [399, 398, 602], [625, 399, 400], [399, 602, 400], [625, 400, 401], [625, 401, 402], [600, 379, 603], [401, 400, 603], [625, 402, 604], [401, 603, 403], [603, 379, 403], [604, 402, 605], [403, 379, 605], [604, 605, 606], [604, 404, 381], [625, 604, 381], [404, 405, 381], [381, 405, 406], [405, 404, 407], [405, 407, 408], [410, 408, 409], [406, 410, 409], [381, 406, 409], [408, 407, 409], [606, 379, 409], [605, 379, 606], [407, 606, 409], [603, 386, 380], [600, 603, 380], [381, 392, 515], [625, 381, 515], [625, 515, 411], [399, 625, 411], [399, 411, 412], [398, 399, 412], [398, 412, 602], [602, 412, 382], [602, 382, 383], [400, 602, 383], [400, 383, 603], [603, 383, 386], [403, 605, 388], [605, 389, 388], [401, 388, 384], [403, 388, 401], [402, 384, 385], [401, 384, 402], [605, 402, 389], [402, 385, 389], [404, 391, 395], [407, 404, 395], [407, 395, 390], [606, 407, 390], [606, 390, 604], [604, 390, 387], [604, 387, 404], [404, 387, 391], [406, 394, 397], [410, 406, 397], [410, 397, 396], [408, 410, 396], [408, 396, 405], [405, 396, 393], [405, 393, 406], [406, 393, 394], [466, 414, 413], [466, 413, 415], [466, 415, 416], [466, 416, 417], [466, 417, 418], [466, 418, 419], [466, 419, 420], [466, 420, 421], [466, 421, 422], [466, 422, 423], [466, 423, 424], [466, 424, 425], [414, 466, 426], [414, 426, 427], [427, 426, 428], [415, 413, 428], [428, 426, 429], [415, 428, 429], [429, 426, 430], [417, 416, 430], [430, 426, 431], [417, 430, 431], [431, 426, 432], [419, 418, 432], [435, 434, 433], [437, 436, 433], [439, 438, 433], [425, 440, 433], [442, 441, 433], [443, 435, 433], [434, 437, 433], [436, 439, 433], [438, 442, 433], [441, 444, 433], [466, 425, 433], [440, 443, 433], [419, 432, 445], [432, 426, 445], [421, 420, 446], [421, 446, 447], [423, 422, 448], [423, 448, 449], [425, 424, 450], [425, 450, 451], [443, 440, 452], [443, 452, 453], [434, 435, 454], [435, 455, 454], [436, 437, 456], [436, 456, 457], [438, 439, 458], [438, 458, 459], [441, 442, 460], [441, 460, 461], [433, 444, 462], [458, 457, 465], [460, 459, 465], [433, 462, 465], [462, 461, 465], [459, 458, 465], [461, 460, 465], [445, 426, 465], [446, 445, 465], [447, 446, 465], [448, 447, 465], [449, 448, 465], [450, 449, 465], [451, 450, 465], [452, 451, 465], [453, 452, 465], [455, 453, 465], [454, 455, 465], [456, 454, 465], [457, 456, 465], [466, 516, 463], [466, 463, 426], [465, 463, 464], [465, 426, 463], [433, 515, 516], [433, 516, 466], [515, 433, 465], [515, 465, 464], [468, 467, 416], [467, 430, 416], [469, 430, 467], [469, 429, 430], [469, 470, 429], [470, 415, 429], [468, 415, 470], [468, 416, 415], [472, 471, 420], [471, 446, 420], [473, 446, 471], [473, 445, 446], [473, 474, 445], [474, 419, 445], [472, 419, 474], [472, 420, 419], [476, 475, 424], [475, 450, 424], [477, 450, 475], [477, 449, 450], [477, 478, 449], [478, 423, 449], [476, 423, 478], [476, 424, 423], [480, 479, 440], [479, 452, 440], [481, 452, 479], [481, 451, 452], [481, 482, 451], [482, 425, 451], [482, 440, 425], [480, 440, 482], [484, 483, 435], [483, 455, 435], [485, 455, 483], [485, 453, 455], [485, 486, 453], [486, 443, 453], [486, 435, 443], [484, 435, 486], [488, 487, 437], [487, 456, 437], [489, 456, 487], [489, 454, 456], [489, 490, 454], [490, 434, 454], [488, 434, 490], [488, 437, 434], [492, 491, 439], [491, 458, 439], [493, 458, 491], [493, 457, 458], [493, 494, 457], [494, 436, 457], [494, 439, 436], [492, 439, 494], [496, 495, 442], [495, 460, 442], [497, 460, 495], [497, 459, 460], [497, 498, 459], [498, 438, 459], [498, 442, 438], [496, 442, 498], [500, 499, 444], [499, 462, 444], [501, 462, 499], [501, 461, 462], [501, 441, 461], [502, 441, 501], [502, 444, 441], [500, 444, 502], [504, 503, 413], [503, 428, 413], [505, 428, 503], [505, 427, 428], [505, 506, 427], [506, 414, 427], [506, 413, 414], [504, 413, 506], [508, 507, 418], [507, 432, 418], [509, 432, 507], [509, 431, 432], [509, 510, 431], [510, 417, 431], [510, 418, 417], [508, 418, 510], [512, 511, 422], [511, 448, 422], [513, 448, 511], [513, 447, 448], [513, 514, 447], [514, 421, 447], [512, 421, 514], [512, 422, 421], [515, 500, 502], [515, 502, 496], [515, 496, 498], [515, 498, 492], [515, 492, 494], [515, 494, 488], [515, 488, 490], [515, 490, 484], [515, 484, 486], [515, 486, 480], [515, 480, 482], [515, 464, 499], [500, 515, 499], [499, 464, 501], [496, 502, 501], [501, 464, 495], [496, 501, 495], [495, 464, 497], [497, 464, 491], [498, 497, 491], [492, 498, 491], [491, 464, 493], [514, 472, 516], [474, 508, 516], [510, 468, 516], [470, 504, 516], [472, 474, 516], [468, 470, 516], [476, 478, 516], [512, 514, 516], [508, 510, 516], [504, 506, 516], [515, 482, 516], [482, 476, 516], [478, 512, 516], [494, 493, 487], [488, 494, 487], [490, 489, 483], [484, 490, 483], [480, 486, 485], [480, 485, 479], [482, 481, 475], [476, 482, 475], [478, 477, 511], [512, 478, 511], [472, 514, 471], [514, 513, 471], [508, 474, 507], [474, 473, 507], [468, 510, 467], [510, 509, 467], [504, 470, 503], [470, 469, 503], [516, 506, 505], [469, 467, 463], [509, 507, 463], [516, 505, 463], [507, 473, 463], [467, 509, 463], [505, 503, 463], [503, 469, 463], [513, 464, 463], [511, 464, 513], [477, 464, 511], [475, 464, 477], [481, 464, 475], [479, 464, 481], [485, 464, 479], [483, 464, 485], [489, 464, 483], [487, 464, 489], [493, 464, 487], [471, 513, 463], [473, 471, 463], [519, 518, 517], [517, 518, 520], [522, 521, 518], [519, 522, 518], [517, 520, 523], [524, 517, 523], [520, 526, 525], [520, 527, 526], [520, 528, 527], [520, 529, 528], [520, 530, 529], [520, 531, 530], [520, 532, 531], [520, 533, 532], [525, 534, 520], [527, 535, 526], [535, 523, 534], [534, 523, 520], [527, 536, 535], [535, 536, 523], [536, 537, 523], [539, 518, 538], [533, 518, 540], [542, 518, 541], [544, 518, 543], [520, 518, 533], [540, 518, 542], [541, 518, 539], [538, 518, 544], [528, 545, 537], [529, 545, 528], [537, 545, 523], [545, 546, 523], [518, 547, 543], [518, 548, 547], [530, 549, 546], [531, 549, 530], [546, 549, 523], [549, 550, 523], [532, 551, 550], [550, 551, 523], [533, 551, 532], [540, 553, 552], [542, 553, 540], [541, 555, 554], [539, 555, 541], [538, 557, 556], [544, 557, 538], [547, 558, 543], [547, 559, 558], [556, 521, 555], [559, 521, 558], [554, 521, 553], [558, 521, 557], [560, 521, 559], [548, 521, 560], [553, 521, 552], [518, 521, 548], [555, 521, 554], [557, 521, 556], [551, 521, 523], [552, 521, 551], [517, 562, 561], [517, 561, 563], [517, 563, 564], [517, 564, 565], [517, 565, 566], [517, 566, 567], [517, 567, 568], [517, 568, 569], [562, 517, 570], [563, 561, 571], [571, 570, 524], [570, 517, 524], [563, 571, 572], [571, 524, 572], [572, 524, 573], [569, 574, 519], [576, 575, 519], [578, 577, 519], [580, 579, 519], [517, 569, 519], [574, 576, 519], [575, 578, 519], [577, 580, 519], [564, 573, 581], [565, 564, 581], [573, 524, 581], [581, 524, 582], [519, 579, 583], [519, 583, 584], [566, 582, 585], [567, 566, 585], [582, 524, 585], [585, 524, 586], [586, 524, 587], [568, 586, 587], [569, 568, 587], [574, 589, 588], [576, 574, 588], [575, 591, 590], [578, 575, 590], [580, 577, 592], [580, 592, 593], [579, 595, 594], [583, 579, 594], [519, 584, 596], [590, 591, 522], [593, 592, 522], [594, 595, 522], [519, 596, 522], [591, 588, 522], [592, 590, 522], [595, 593, 522], [596, 594, 522], [588, 589, 522], [589, 524, 522], [587, 524, 589], [524, 523, 522], [522, 523, 521], [577, 578, 539], [577, 539, 538], [577, 538, 592], [592, 538, 556], [592, 556, 555], [590, 592, 555], [590, 555, 578], [578, 555, 539], [571, 526, 535], [561, 526, 571], [571, 534, 570], [571, 535, 534], [562, 534, 525], [570, 534, 562], [561, 525, 526], [562, 525, 561], [573, 528, 537], [564, 528, 573], [573, 536, 572], [573, 537, 536], [563, 536, 527], [572, 536, 563], [564, 527, 528], [563, 527, 564], [582, 530, 546], [566, 530, 582], [582, 545, 581], [582, 546, 545], [565, 545, 529], [581, 545, 565], [565, 530, 566], [565, 529, 530], [586, 568, 550], [568, 532, 550], [585, 550, 549], [586, 550, 585], [567, 549, 531], [585, 549, 567], [567, 532, 568], [567, 531, 532], [589, 574, 552], [574, 540, 552], [587, 589, 551], [589, 552, 551], [569, 587, 533], [587, 551, 533], [574, 533, 540], [569, 533, 574], [591, 575, 554], [575, 541, 554], [591, 553, 588], [591, 554, 553], [576, 588, 542], [588, 553, 542], [576, 541, 575], [576, 542, 541], [595, 579, 558], [579, 543, 558], [595, 557, 593], [595, 558, 557], [580, 593, 544], [593, 557, 544], [579, 544, 543], [580, 544, 579], [584, 583, 547], [584, 547, 548], [584, 548, 596], [596, 548, 560], [596, 560, 559], [594, 596, 559], [594, 559, 583], [583, 559, 547], [347, 346, 381], [346, 409, 381], [346, 629, 409], [597, 629, 346], [597, 598, 629], [598, 633, 629], [598, 381, 633], [347, 381, 598], [409, 633, 381], [629, 633, 409], [346, 347, 598], [597, 346, 598], [599, 379, 409], [623, 599, 409], [601, 600, 599], [599, 600, 379], [623, 409, 381], [617, 623, 381], [399, 602, 398], [625, 400, 399], [399, 400, 602], [625, 401, 400], [625, 402, 401], [600, 603, 379], [401, 603, 400], [625, 604, 402], [401, 403, 603], [603, 403, 379], [604, 605, 402], [403, 605, 379], [604, 606, 605], [604, 381, 404], [625, 381, 604], [404, 381, 405], [381, 406, 405], [405, 407, 404], [405, 408, 407], [406, 409, 410], [410, 409, 408], [381, 409, 406], [408, 409, 407], [605, 409, 379], [606, 409, 605], [407, 409, 606], [627, 608, 607], [626, 627, 609], [627, 607, 609], [626, 609, 610], [626, 610, 611], [601, 599, 612], [610, 609, 612], [626, 611, 613], [610, 612, 614], [612, 599, 614], [613, 611, 615], [614, 599, 615], [613, 615, 616], [613, 618, 617], [626, 613, 617], [618, 619, 617], [617, 619, 620], [619, 618, 621], [619, 621, 622], [624, 622, 623], [620, 624, 623], [617, 620, 623], [622, 621, 623], [616, 599, 623], [615, 599, 616], [621, 616, 623], [612, 603, 600], [601, 612, 600], [617, 381, 625], [626, 617, 625], [626, 625, 399], [627, 626, 399], [627, 399, 398], [608, 627, 398], [608, 398, 607], [607, 398, 602], [607, 602, 400], [609, 607, 400], [609, 400, 612], [612, 400, 603], [614, 615, 403], [615, 605, 403], [610, 403, 401], [614, 403, 610], [611, 401, 402], [610, 401, 611], [615, 611, 605], [611, 402, 605], [618, 404, 407], [621, 618, 407], [621, 407, 606], [616, 621, 606], [616, 606, 613], [613, 606, 604], [613, 604, 618], [618, 604, 404], [620, 406, 410], [624, 620, 410], [624, 410, 408], [622, 624, 408], [622, 408, 619], [619, 408, 405], [619, 405, 620], [620, 405, 406], [630, 628, 652], [629, 630, 652], [632, 631, 630], [630, 631, 628], [629, 652, 646], [633, 629, 646], [636, 635, 634], [725, 637, 636], [636, 637, 635], [725, 638, 637], [725, 639, 638], [631, 640, 628], [638, 640, 637], [725, 641, 639], [638, 642, 640], [640, 642, 628], [641, 643, 639], [642, 643, 628], [641, 644, 643], [641, 646, 645], [725, 646, 641], [645, 646, 647], [646, 648, 647], [647, 649, 645], [647, 650, 649], [648, 652, 651], [651, 652, 650], [646, 652, 648], [650, 652, 649], [643, 652, 628], [644, 652, 643], [649, 652, 644], [655, 654, 653], [657, 655, 656], [655, 653, 656], [657, 656, 658], [657, 658, 659], [632, 630, 660], [658, 656, 660], [657, 659, 661], [658, 660, 662], [660, 630, 662], [661, 659, 663], [662, 630, 663], [661, 663, 664], [661, 665, 633], [657, 661, 633], [665, 666, 633], [633, 666, 667], [666, 665, 668], [666, 668, 669], [670, 669, 629], [667, 670, 629], [633, 667, 629], [669, 668, 629], [664, 630, 629], [663, 630, 664], [668, 664, 629], [660, 640, 631], [632, 660, 631], [633, 646, 725], [657, 633, 725], [657, 725, 636], [655, 657, 636], [655, 636, 634], [654, 655, 634], [654, 634, 653], [653, 634, 635], [653, 635, 637], [656, 653, 637], [656, 637, 660], [660, 637, 640], [662, 663, 642], [663, 643, 642], [658, 642, 638], [662, 642, 658], [659, 638, 639], [658, 638, 659], [663, 659, 643], [659, 639, 643], [665, 645, 649], [668, 665, 649], [668, 649, 644], [664, 668, 644], [664, 644, 661], [661, 644, 641], [661, 641, 665], [665, 641, 645], [667, 648, 651], [670, 667, 651], [670, 651, 650], [669, 670, 650], [669, 650, 666], [666, 650, 647], [666, 647, 667], [667, 647, 648], [723, 672, 671], [723, 671, 673], [723, 673, 674], [723, 674, 675], [723, 675, 676], [723, 676, 677], [723, 677, 678], [723, 678, 679], [723, 679, 680], [723, 680, 681], [723, 681, 682], [723, 682, 683], [672, 723, 684], [672, 684, 685], [685, 684, 686], [673, 671, 686], [686, 684, 687], [673, 686, 687], [687, 684, 688], [675, 674, 688], [688, 684, 689], [675, 688, 689], [689, 684, 690], [677, 676, 690], [692, 691, 724], [694, 693, 724], [696, 695, 724], [683, 697, 724], [699, 698, 724], [700, 692, 724], [691, 694, 724], [693, 696, 724], [695, 699, 724], [698, 701, 724], [723, 683, 724], [697, 700, 724], [677, 690, 702], [690, 684, 702], [679, 678, 703], [679, 703, 704], [681, 680, 705], [681, 705, 706], [683, 682, 707], [683, 707, 708], [700, 697, 709], [700, 709, 710], [691, 692, 711], [692, 712, 711], [693, 694, 713], [693, 713, 714], [695, 696, 715], [695, 715, 716], [698, 699, 717], [698, 717, 718], [724, 701, 719], [715, 714, 720], [717, 716, 720], [724, 719, 720], [719, 718, 720], [716, 715, 720], [718, 717, 720], [702, 684, 720], [703, 702, 720], [704, 703, 720], [705, 704, 720], [706, 705, 720], [707, 706, 720], [708, 707, 720], [709, 708, 720], [710, 709, 720], [712, 710, 720], [711, 712, 720], [713, 711, 720], [714, 713, 720], [723, 722, 721], [723, 721, 684], [720, 721, 626], [720, 684, 721], [724, 725, 722], [724, 722, 723], [725, 724, 720], [725, 720, 626], [727, 726, 674], [726, 688, 674], [728, 688, 726], [728, 687, 688], [728, 729, 687], [729, 673, 687], [727, 673, 729], [727, 674, 673], [731, 730, 678], [730, 703, 678], [732, 703, 730], [732, 702, 703], [732, 733, 702], [733, 677, 702], [731, 677, 733], [731, 678, 677], [735, 734, 682], [734, 707, 682], [736, 707, 734], [736, 706, 707], [736, 737, 706], [737, 681, 706], [735, 681, 737], [735, 682, 681], [739, 738, 697], [738, 709, 697], [740, 709, 738], [740, 708, 709], [740, 741, 708], [741, 683, 708], [741, 697, 683], [739, 697, 741], [743, 742, 692], [742, 712, 692], [744, 712, 742], [744, 710, 712], [744, 745, 710], [745, 700, 710], [745, 692, 700], [743, 692, 745], [747, 746, 694], [746, 713, 694], [748, 713, 746], [748, 711, 713], [748, 749, 711], [749, 691, 711], [747, 691, 749], [747, 694, 691], [751, 750, 696], [750, 715, 696], [752, 715, 750], [752, 714, 715], [752, 753, 714], [753, 693, 714], [753, 696, 693], [751, 696, 753], [755, 754, 699], [754, 717, 699], [756, 717, 754], [756, 716, 717], [756, 757, 716], [757, 695, 716], [757, 699, 695], [755, 699, 757], [759, 758, 701], [758, 719, 701], [760, 719, 758], [760, 718, 719], [760, 698, 718], [761, 698, 760], [761, 701, 698], [759, 701, 761], [763, 762, 671], [762, 686, 671], [764, 686, 762], [764, 685, 686], [764, 765, 685], [765, 672, 685], [765, 671, 672], [763, 671, 765], [767, 766, 676], [766, 690, 676], [768, 690, 766], [768, 689, 690], [768, 769, 689], [769, 675, 689], [769, 676, 675], [767, 676, 769], [771, 770, 680], [770, 705, 680], [772, 705, 770], [772, 704, 705], [772, 773, 704], [773, 679, 704], [771, 679, 773], [771, 680, 679], [725, 759, 761], [725, 761, 755], [725, 755, 757], [725, 757, 751], [725, 751, 753], [725, 753, 747], [725, 747, 749], [725, 749, 743], [725, 743, 745], [725, 745, 739], [725, 739, 741], [725, 626, 758], [759, 725, 758], [758, 626, 760], [755, 761, 760], [760, 626, 754], [755, 760, 754], [754, 626, 756], [756, 626, 750], [757, 756, 750], [751, 757, 750], [750, 626, 752], [773, 731, 722], [733, 767, 722], [769, 727, 722], [729, 763, 722], [731, 733, 722], [727, 729, 722], [735, 737, 722], [771, 773, 722], [767, 769, 722], [763, 765, 722], [725, 741, 722], [741, 735, 722], [737, 771, 722], [753, 752, 746], [747, 753, 746], [749, 748, 742], [743, 749, 742], [739, 745, 744], [739, 744, 738], [741, 740, 734], [735, 741, 734], [737, 736, 770], [771, 737, 770], [731, 773, 730], [773, 772, 730], [767, 733, 766], [733, 732, 766], [727, 769, 726], [769, 768, 726], [763, 729, 762], [729, 728, 762], [722, 765, 764], [728, 726, 721], [768, 766, 721], [722, 764, 721], [766, 732, 721], [726, 768, 721], [764, 762, 721], [762, 728, 721], [772, 626, 721], [770, 626, 772], [736, 626, 770], [734, 626, 736], [740, 626, 734], [738, 626, 740], [744, 626, 738], [742, 626, 744], [748, 626, 742], [746, 626, 748], [752, 626, 746], [730, 772, 721], [732, 730, 721], [776, 775, 774], [774, 775, 777], [779, 778, 775], [776, 779, 775], [774, 777, 780], [781, 774, 780], [777, 783, 782], [777, 784, 783], [777, 785, 784], [777, 786, 785], [777, 787, 786], [777, 788, 787], [777, 789, 788], [777, 790, 789], [782, 791, 777], [784, 792, 783], [792, 780, 791], [791, 780, 777], [784, 793, 792], [792, 793, 780], [793, 794, 780], [796, 775, 795], [790, 775, 797], [799, 775, 798], [801, 775, 800], [777, 775, 790], [797, 775, 799], [798, 775, 796], [795, 775, 801], [785, 802, 794], [786, 802, 785], [794, 802, 780], [802, 803, 780], [775, 804, 800], [775, 805, 804], [787, 806, 803], [788, 806, 787], [803, 806, 780], [806, 807, 780], [789, 808, 807], [807, 808, 780], [790, 808, 789], [797, 810, 809], [799, 810, 797], [798, 812, 811], [796, 812, 798], [795, 814, 813], [801, 814, 795], [804, 815, 800], [804, 816, 815], [813, 778, 812], [816, 778, 815], [811, 778, 810], [815, 778, 814], [817, 778, 816], [805, 778, 817], [810, 778, 809], [775, 778, 805], [812, 778, 811], [814, 778, 813], [808, 778, 780], [809, 778, 808], [774, 819, 818], [774, 818, 820], [774, 820, 821], [774, 821, 822], [774, 822, 823], [774, 823, 824], [774, 824, 825], [774, 825, 826], [819, 774, 827], [820, 818, 828], [828, 827, 781], [827, 774, 781], [820, 828, 829], [828, 781, 829], [829, 781, 830], [826, 831, 776], [833, 832, 776], [835, 834, 776], [837, 836, 776], [774, 826, 776], [831, 833, 776], [832, 835, 776], [834, 837, 776], [821, 830, 838], [822, 821, 838], [830, 781, 838], [838, 781, 839], [776, 836, 840], [776, 840, 841], [823, 839, 842], [824, 823, 842], [839, 781, 842], [842, 781, 843], [843, 781, 844], [825, 843, 844], [826, 825, 844], [831, 846, 845], [833, 831, 845], [832, 848, 847], [835, 832, 847], [837, 834, 849], [837, 849, 850], [836, 852, 851], [840, 836, 851], [776, 841, 853], [847, 848, 779], [850, 849, 779], [851, 852, 779], [776, 853, 779], [848, 845, 779], [849, 847, 779], [852, 850, 779], [853, 851, 779], [845, 846, 779], [846, 781, 779], [844, 781, 846], [781, 780, 779], [779, 780, 778], [834, 835, 796], [834, 796, 795], [834, 795, 849], [849, 795, 813], [849, 813, 812], [847, 849, 812], [847, 812, 835], [835, 812, 796], [828, 783, 792], [818, 783, 828], [828, 791, 827], [828, 792, 791], [819, 791, 782], [827, 791, 819], [818, 782, 783], [819, 782, 818], [830, 785, 794], [821, 785, 830], [830, 793, 829], [830, 794, 793], [820, 793, 784], [829, 793, 820], [821, 784, 785], [820, 784, 821], [839, 787, 803], [823, 787, 839], [839, 802, 838], [839, 803, 802], [822, 802, 786], [838, 802, 822], [822, 787, 823], [822, 786, 787], [843, 825, 807], [825, 789, 807], [842, 807, 806], [843, 807, 842], [824, 806, 788], [842, 806, 824], [824, 789, 825], [824, 788, 789], [846, 831, 809], [831, 797, 809], [844, 846, 808], [846, 809, 808], [826, 844, 790], [844, 808, 790], [831, 790, 797], [826, 790, 831], [848, 832, 811], [832, 798, 811], [848, 810, 845], [848, 811, 810], [833, 845, 799], [845, 810, 799], [833, 798, 832], [833, 799, 798], [852, 836, 815], [836, 800, 815], [852, 814, 850], [852, 815, 814], [837, 850, 801], [850, 814, 801], [836, 801, 800], [837, 801, 836], [841, 840, 804], [841, 804, 805], [841, 805, 853], [853, 805, 817], [853, 817, 816], [851, 853, 816], [851, 816, 840], [840, 816, 804], [855, 854, 878], [854, 856, 878], [854, 857, 856], [858, 857, 854], [858, 859, 857], [859, 907, 857], [859, 878, 907], [855, 878, 859], [856, 907, 878], [857, 907, 856], [854, 855, 859], [858, 854, 859], [862, 860, 856], [861, 862, 856], [864, 863, 862], [862, 863, 860], [861, 856, 878], [865, 861, 878], [867, 866, 901], [869, 868, 867], [867, 868, 866], [869, 870, 868], [869, 871, 870], [863, 872, 860], [870, 872, 868], [869, 873, 871], [870, 874, 872], [872, 874, 860], [873, 875, 871], [874, 875, 860], [873, 876, 875], [873, 878, 877], [869, 878, 873], [877, 878, 879], [878, 880, 879], [879, 881, 877], [879, 882, 881], [880, 856, 883], [883, 856, 882], [878, 856, 880], [882, 856, 881], [875, 856, 860], [876, 856, 875], [881, 856, 876], [900, 902, 884], [886, 900, 885], [900, 884, 885], [886, 885, 887], [886, 887, 888], [864, 862, 889], [887, 885, 889], [886, 888, 890], [887, 889, 891], [889, 862, 891], [890, 888, 892], [891, 862, 892], [890, 892, 893], [890, 894, 865], [886, 890, 865], [894, 895, 865], [865, 895, 896], [895, 894, 897], [895, 897, 898], [899, 898, 861], [896, 899, 861], [865, 896, 861], [898, 897, 861], [893, 862, 861], [892, 862, 893], [897, 893, 861], [889, 872, 863], [864, 889, 863], [865, 878, 869], [886, 865, 869], [886, 869, 867], [900, 886, 867], [900, 867, 901], [902, 900, 901], [902, 901, 884], [884, 901, 866], [884, 866, 868], [885, 884, 868], [885, 868, 889], [889, 868, 872], [891, 892, 874], [892, 875, 874], [887, 874, 870], [891, 874, 887], [888, 870, 871], [887, 870, 888], [892, 888, 875], [888, 871, 875], [894, 877, 881], [897, 894, 881], [897, 881, 876], [893, 897, 876], [893, 876, 890], [890, 876, 873], [890, 873, 894], [894, 873, 877], [896, 880, 883], [899, 896, 883], [899, 883, 882], [898, 899, 882], [898, 882, 895], [895, 882, 879], [895, 879, 896], [896, 879, 880], [904, 903, 927], [857, 904, 927], [906, 905, 904], [904, 905, 903], [857, 927, 921], [907, 857, 921], [910, 909, 908], [912, 911, 910], [910, 911, 909], [912, 913, 911], [912, 914, 913], [905, 915, 903], [913, 915, 911], [912, 916, 914], [913, 917, 915], [915, 917, 903], [916, 918, 914], [917, 918, 903], [916, 919, 918], [916, 921, 920], [912, 921, 916], [920, 921, 922], [921, 923, 922], [922, 924, 920], [922, 925, 924], [923, 927, 926], [926, 927, 925], [921, 927, 923], [925, 927, 924], [918, 927, 903], [919, 927, 918], [924, 927, 919], [929, 945, 928], [931, 929, 930], [929, 928, 930], [931, 930, 932], [931, 932, 933], [906, 904, 934], [932, 930, 934], [931, 933, 935], [932, 934, 936], [934, 904, 936], [935, 933, 937], [936, 904, 937], [935, 937, 938], [935, 939, 907], [931, 935, 907], [939, 940, 907], [907, 940, 941], [940, 939, 942], [940, 942, 943], [944, 943, 857], [941, 944, 857], [907, 941, 857], [943, 942, 857], [938, 904, 857], [937, 904, 938], [942, 938, 857], [934, 915, 905], [906, 934, 905], [907, 921, 912], [931, 907, 912], [931, 912, 910], [929, 931, 910], [929, 910, 908], [945, 929, 908], [945, 908, 928], [928, 908, 909], [928, 909, 911], [930, 928, 911], [930, 911, 934], [934, 911, 915], [936, 937, 917], [937, 918, 917], [932, 917, 913], [936, 917, 932], [933, 913, 914], [932, 913, 933], [937, 933, 918], [933, 914, 918], [939, 920, 924], [942, 939, 924], [942, 924, 919], [938, 942, 919], [938, 919, 935], [935, 919, 916], [935, 916, 939], [939, 916, 920], [941, 923, 926], [944, 941, 926], [944, 926, 925], [943, 944, 925], [943, 925, 940], [940, 925, 922], [940, 922, 941], [941, 922, 923], [999, 947, 946], [999, 946, 948], [999, 948, 949], [999, 949, 950], [999, 950, 951], [999, 951, 952], [999, 952, 953], [999, 953, 954], [999, 954, 955], [999, 955, 956], [999, 956, 957], [999, 957, 958], [947, 999, 959], [947, 959, 960], [960, 959, 961], [948, 946, 961], [961, 959, 962], [948, 961, 962], [962, 959, 963], [950, 949, 963], [963, 959, 964], [950, 963, 964], [964, 959, 965], [952, 951, 965], [968, 967, 966], [970, 969, 966], [972, 971, 966], [958, 973, 966], [975, 974, 966], [976, 968, 966], [967, 970, 966], [969, 972, 966], [971, 975, 966], [974, 977, 966], [999, 958, 966], [973, 976, 966], [952, 965, 978], [965, 959, 978], [954, 953, 979], [954, 979, 980], [956, 955, 981], [956, 981, 982], [958, 957, 983], [958, 983, 984], [976, 973, 985], [976, 985, 986], [967, 968, 987], [968, 988, 987], [969, 970, 989], [969, 989, 990], [971, 972, 991], [971, 991, 992], [974, 975, 993], [974, 993, 994], [966, 977, 995], [991, 990, 996], [993, 992, 996], [966, 995, 996], [995, 994, 996], [992, 991, 996], [994, 993, 996], [978, 959, 996], [979, 978, 996], [980, 979, 996], [981, 980, 996], [982, 981, 996], [983, 982, 996], [984, 983, 996], [985, 984, 996], [986, 985, 996], [988, 986, 996], [987, 988, 996], [989, 987, 996], [990, 989, 996], [999, 997, 998], [999, 998, 959], [996, 998, 886], [996, 959, 998], [966, 912, 997], [966, 997, 999], [912, 966, 996], [912, 996, 886], [1001, 1000, 949], [1000, 963, 949], [1002, 963, 1000], [1002, 962, 963], [1002, 1003, 962], [1003, 948, 962], [1001, 948, 1003], [1001, 949, 948], [1005, 1004, 953], [1004, 979, 953], [1006, 979, 1004], [1006, 978, 979], [1006, 1007, 978], [1007, 952, 978], [1005, 952, 1007], [1005, 953, 952], [1009, 1008, 957], [1008, 983, 957], [1010, 983, 1008], [1010, 982, 983], [1010, 1011, 982], [1011, 956, 982], [1009, 956, 1011], [1009, 957, 956], [1013, 1012, 973], [1012, 985, 973], [1014, 985, 1012], [1014, 984, 985], [1014, 1015, 984], [1015, 958, 984], [1015, 973, 958], [1013, 973, 1015], [1017, 1016, 968], [1016, 988, 968], [1018, 988, 1016], [1018, 986, 988], [1018, 1019, 986], [1019, 976, 986], [1019, 968, 976], [1017, 968, 1019], [1021, 1020, 970], [1020, 989, 970], [1022, 989, 1020], [1022, 987, 989], [1022, 1023, 987], [1023, 967, 987], [1021, 967, 1023], [1021, 970, 967], [1025, 1024, 972], [1024, 991, 972], [1026, 991, 1024], [1026, 990, 991], [1026, 1027, 990], [1027, 969, 990], [1027, 972, 969], [1025, 972, 1027], [1029, 1028, 975], [1028, 993, 975], [1030, 993, 1028], [1030, 992, 993], [1030, 1031, 992], [1031, 971, 992], [1031, 975, 971], [1029, 975, 1031], [1033, 1032, 977], [1032, 995, 977], [1034, 995, 1032], [1034, 994, 995], [1034, 974, 994], [1035, 974, 1034], [1035, 977, 974], [1033, 977, 1035], [1037, 1036, 946], [1036, 961, 946], [1038, 961, 1036], [1038, 960, 961], [1038, 1039, 960], [1039, 947, 960], [1039, 946, 947], [1037, 946, 1039], [1041, 1040, 951], [1040, 965, 951], [1042, 965, 1040], [1042, 964, 965], [1042, 1043, 964], [1043, 950, 964], [1043, 951, 950], [1041, 951, 1043], [1045, 1044, 955], [1044, 981, 955], [1046, 981, 1044], [1046, 980, 981], [1046, 1047, 980], [1047, 954, 980], [1045, 954, 1047], [1045, 955, 954], [912, 1033, 1035], [912, 1035, 1029], [912, 1029, 1031], [912, 1031, 1025], [912, 1025, 1027], [912, 1027, 1021], [912, 1021, 1023], [912, 1023, 1017], [912, 1017, 1019], [912, 1019, 1013], [912, 1013, 1015], [912, 886, 1032], [1033, 912, 1032], [1032, 886, 1034], [1029, 1035, 1034], [1034, 886, 1028], [1029, 1034, 1028], [1028, 886, 1030], [1030, 886, 1024], [1031, 1030, 1024], [1025, 1031, 1024], [1024, 886, 1026], [1047, 1005, 997], [1007, 1041, 997], [1043, 1001, 997], [1003, 1037, 997], [1005, 1007, 997], [1001, 1003, 997], [1009, 1011, 997], [1045, 1047, 997], [1041, 1043, 997], [1037, 1039, 997], [912, 1015, 997], [1015, 1009, 997], [1011, 1045, 997], [1027, 1026, 1020], [1021, 1027, 1020], [1023, 1022, 1016], [1017, 1023, 1016], [1013, 1019, 1018], [1013, 1018, 1012], [1015, 1014, 1008], [1009, 1015, 1008], [1011, 1010, 1044], [1045, 1011, 1044], [1005, 1047, 1004], [1047, 1046, 1004], [1041, 1007, 1040], [1007, 1006, 1040], [1001, 1043, 1000], [1043, 1042, 1000], [1037, 1003, 1036], [1003, 1002, 1036], [997, 1039, 1038], [1002, 1000, 998], [1042, 1040, 998], [997, 1038, 998], [1040, 1006, 998], [1000, 1042, 998], [1038, 1036, 998], [1036, 1002, 998], [1046, 886, 998], [1044, 886, 1046], [1010, 886, 1044], [1008, 886, 1010], [1014, 886, 1008], [1012, 886, 1014], [1018, 886, 1012], [1016, 886, 1018], [1022, 886, 1016], [1020, 886, 1022], [1026, 886, 1020], [1004, 1046, 998], [1006, 1004, 998], [1050, 1049, 1048], [1048, 1049, 1051], [1127, 1052, 1049], [1050, 1127, 1049], [1048, 1051, 1053], [1054, 1048, 1053], [1051, 1056, 1055], [1051, 1057, 1056], [1051, 1058, 1057], [1051, 1059, 1058], [1051, 1060, 1059], [1051, 1061, 1060], [1051, 1062, 1061], [1051, 1063, 1062], [1055, 1064, 1051], [1057, 1065, 1056], [1065, 1053, 1064], [1064, 1053, 1051], [1057, 1066, 1065], [1065, 1066, 1053], [1066, 1067, 1053], [1069, 1049, 1068], [1063, 1049, 1070], [1072, 1049, 1071], [1074, 1049, 1073], [1051, 1049, 1063], [1070, 1049, 1072], [1071, 1049, 1069], [1068, 1049, 1074], [1058, 1075, 1067], [1059, 1075, 1058], [1067, 1075, 1053], [1075, 1076, 1053], [1049, 1077, 1073], [1049, 1078, 1077], [1060, 1079, 1076], [1061, 1079, 1060], [1076, 1079, 1053], [1079, 1080, 1053], [1062, 1081, 1080], [1080, 1081, 1053], [1063, 1081, 1062], [1070, 1083, 1082], [1072, 1083, 1070], [1071, 1085, 1084], [1069, 1085, 1071], [1068, 1087, 1086], [1074, 1087, 1068], [1077, 1088, 1073], [1077, 1089, 1088], [1086, 1052, 1085], [1089, 1052, 1088], [1084, 1052, 1083], [1088, 1052, 1087], [1090, 1052, 1089], [1078, 1052, 1090], [1083, 1052, 1082], [1049, 1052, 1078], [1085, 1052, 1084], [1087, 1052, 1086], [1081, 1052, 1053], [1082, 1052, 1081], [1048, 1092, 1091], [1048, 1091, 1093], [1048, 1093, 1094], [1048, 1094, 1095], [1048, 1095, 1096], [1048, 1096, 1097], [1048, 1097, 1098], [1048, 1098, 1099], [1092, 1048, 1100], [1093, 1091, 1101], [1101, 1100, 1054], [1100, 1048, 1054], [1093, 1101, 1102], [1101, 1054, 1102], [1102, 1054, 1103], [1099, 1104, 1050], [1106, 1105, 1050], [1108, 1107, 1050], [1110, 1109, 1050], [1048, 1099, 1050], [1104, 1106, 1050], [1105, 1108, 1050], [1107, 1110, 1050], [1094, 1103, 1111], [1095, 1094, 1111], [1103, 1054, 1111], [1111, 1054, 1112], [1050, 1109, 1113], [1050, 1113, 1114], [1096, 1112, 1115], [1097, 1096, 1115], [1112, 1054, 1115], [1115, 1054, 1116], [1116, 1054, 1117], [1098, 1116, 1117], [1099, 1098, 1117], [1104, 1119, 1118], [1106, 1104, 1118], [1105, 1121, 1120], [1108, 1105, 1120], [1110, 1107, 1122], [1110, 1122, 1123], [1109, 1125, 1124], [1113, 1109, 1124], [1050, 1114, 1126], [1120, 1121, 1127], [1123, 1122, 1127], [1124, 1125, 1127], [1050, 1126, 1127], [1121, 1118, 1127], [1122, 1120, 1127], [1125, 1123, 1127], [1126, 1124, 1127], [1118, 1119, 1127], [1119, 1054, 1127], [1117, 1054, 1119], [1054, 1053, 1127], [1127, 1053, 1052], [1107, 1108, 1069], [1107, 1069, 1068], [1107, 1068, 1122], [1122, 1068, 1086], [1122, 1086, 1085], [1120, 1122, 1085], [1120, 1085, 1108], [1108, 1085, 1069], [1101, 1056, 1065], [1091, 1056, 1101], [1101, 1064, 1100], [1101, 1065, 1064], [1092, 1064, 1055], [1100, 1064, 1092], [1091, 1055, 1056], [1092, 1055, 1091], [1103, 1058, 1067], [1094, 1058, 1103], [1103, 1066, 1102], [1103, 1067, 1066], [1093, 1066, 1057], [1102, 1066, 1093], [1094, 1057, 1058], [1093, 1057, 1094], [1112, 1060, 1076], [1096, 1060, 1112], [1112, 1075, 1111], [1112, 1076, 1075], [1095, 1075, 1059], [1111, 1075, 1095], [1095, 1060, 1096], [1095, 1059, 1060], [1116, 1098, 1080], [1098, 1062, 1080], [1115, 1080, 1079], [1116, 1080, 1115], [1097, 1079, 1061], [1115, 1079, 1097], [1097, 1062, 1098], [1097, 1061, 1062], [1119, 1104, 1082], [1104, 1070, 1082], [1117, 1119, 1081], [1119, 1082, 1081], [1099, 1117, 1063], [1117, 1081, 1063], [1104, 1063, 1070], [1099, 1063, 1104], [1121, 1105, 1084], [1105, 1071, 1084], [1121, 1083, 1118], [1121, 1084, 1083], [1106, 1118, 1072], [1118, 1083, 1072], [1106, 1071, 1105], [1106, 1072, 1071], [1125, 1109, 1088], [1109, 1073, 1088], [1125, 1087, 1123], [1125, 1088, 1087], [1110, 1123, 1074], [1123, 1087, 1074], [1109, 1074, 1073], [1110, 1074, 1109], [1114, 1113, 1077], [1114, 1077, 1078], [1114, 1078, 1126], [1126, 1078, 1090], [1126, 1090, 1089], [1124, 1126, 1089], [1124, 1089, 1113], [1113, 1089, 1077], [1129, 1128, 1148], [1128, 1130, 1148], [1128, 1216, 1130], [1131, 1216, 1128], [1131, 1132, 1216], [1132, 1181, 1216], [1132, 1148, 1181], [1129, 1148, 1132], [1130, 1181, 1148], [1216, 1181, 1130], [1128, 1129, 1132], [1131, 1128, 1132], [1134, 1133, 1130], [1170, 1134, 1130], [1136, 1135, 1134], [1134, 1135, 1133], [1170, 1130, 1148], [1164, 1170, 1148], [1138, 1137, 1175], [1172, 1139, 1138], [1138, 1139, 1137], [1172, 1140, 1139], [1172, 1141, 1140], [1135, 1142, 1133], [1140, 1142, 1139], [1172, 1143, 1141], [1140, 1144, 1142], [1142, 1144, 1133], [1143, 1145, 1141], [1144, 1145, 1133], [1143, 1146, 1145], [1143, 1148, 1147], [1172, 1148, 1143], [1147, 1148, 1149], [1148, 1150, 1149], [1149, 1151, 1147], [1149, 1152, 1151], [1150, 1130, 1153], [1153, 1130, 1152], [1148, 1130, 1150], [1152, 1130, 1151], [1145, 1130, 1133], [1146, 1130, 1145], [1151, 1130, 1146], [1174, 1155, 1154], [1173, 1174, 1156], [1174, 1154, 1156], [1173, 1156, 1157], [1173, 1157, 1158], [1136, 1134, 1159], [1157, 1156, 1159], [1173, 1158, 1160], [1157, 1159, 1161], [1159, 1134, 1161], [1160, 1158, 1162], [1161, 1134, 1162], [1160, 1162, 1163], [1160, 1165, 1164], [1173, 1160, 1164], [1165, 1166, 1164], [1164, 1166, 1167], [1166, 1165, 1168], [1166, 1168, 1169], [1171, 1169, 1170], [1167, 1171, 1170], [1164, 1167, 1170], [1169, 1168, 1170], [1163, 1134, 1170], [1162, 1134, 1163], [1168, 1163, 1170], [1159, 1142, 1135], [1136, 1159, 1135], [1164, 1148, 1172], [1173, 1164, 1172], [1173, 1172, 1138], [1174, 1173, 1138], [1174, 1138, 1175], [1155, 1174, 1175], [1155, 1175, 1154], [1154, 1175, 1137], [1154, 1137, 1139], [1156, 1154, 1139], [1156, 1139, 1159], [1159, 1139, 1142], [1161, 1162, 1144], [1162, 1145, 1144], [1157, 1144, 1140], [1161, 1144, 1157], [1158, 1140, 1141], [1157, 1140, 1158], [1162, 1158, 1145], [1158, 1141, 1145], [1165, 1147, 1151], [1168, 1165, 1151], [1168, 1151, 1146], [1163, 1168, 1146], [1163, 1146, 1160], [1160, 1146, 1143], [1160, 1143, 1165], [1165, 1143, 1147], [1167, 1150, 1153], [1171, 1167, 1153], [1171, 1153, 1152], [1169, 1171, 1152], [1169, 1152, 1166], [1166, 1152, 1149], [1166, 1149, 1167], [1167, 1149, 1150], [1177, 1176, 1198], [1216, 1177, 1198], [1179, 1178, 1177], [1177, 1178, 1176], [1216, 1198, 1180], [1181, 1216, 1180], [1183, 1182, 1218], [1269, 1184, 1183], [1183, 1184, 1182], [1269, 1185, 1184], [1269, 1186, 1185], [1178, 1187, 1176], [1185, 1187, 1184], [1269, 1188, 1186], [1185, 1189, 1187], [1187, 1189, 1176], [1188, 1190, 1186], [1189, 1190, 1176], [1188, 1191, 1190], [1188, 1180, 1192], [1269, 1180, 1188], [1192, 1180, 1193], [1180, 1194, 1193], [1193, 1195, 1192], [1193, 1196, 1195], [1194, 1198, 1197], [1197, 1198, 1196], [1180, 1198, 1194], [1196, 1198, 1195], [1190, 1198, 1176], [1191, 1198, 1190], [1195, 1198, 1191], [1201, 1200, 1199], [1203, 1201, 1202], [1201, 1199, 1202], [1203, 1202, 1204], [1203, 1204, 1205], [1179, 1177, 1206], [1204, 1202, 1206], [1203, 1205, 1207], [1204, 1206, 1208], [1206, 1177, 1208], [1207, 1205, 1209], [1208, 1177, 1209], [1207, 1209, 1210], [1207, 1211, 1181], [1203, 1207, 1181], [1211, 1212, 1181], [1181, 1212, 1213], [1212, 1211, 1214], [1212, 1214, 1215], [1217, 1215, 1216], [1213, 1217, 1216], [1181, 1213, 1216], [1215, 1214, 1216], [1210, 1177, 1216], [1209, 1177, 1210], [1214, 1210, 1216], [1206, 1187, 1178], [1179, 1206, 1178], [1181, 1180, 1269], [1203, 1181, 1269], [1203, 1269, 1183], [1201, 1203, 1183], [1201, 1183, 1218], [1200, 1201, 1218], [1200, 1218, 1199], [1199, 1218, 1182], [1199, 1182, 1184], [1202, 1199, 1184], [1202, 1184, 1206], [1206, 1184, 1187], [1208, 1209, 1189], [1209, 1190, 1189], [1204, 1189, 1185], [1208, 1189, 1204], [1205, 1185, 1186], [1204, 1185, 1205], [1209, 1205, 1190], [1205, 1186, 1190], [1211, 1192, 1195], [1214, 1211, 1195], [1214, 1195, 1191], [1210, 1214, 1191], [1210, 1191, 1207], [1207, 1191, 1188], [1207, 1188, 1211], [1211, 1188, 1192], [1213, 1194, 1197], [1217, 1213, 1197], [1217, 1197, 1196], [1215, 1217, 1196], [1215, 1196, 1212], [1212, 1196, 1193], [1212, 1193, 1213], [1213, 1193, 1194], [1270, 1220, 1219], [1270, 1219, 1221], [1270, 1221, 1222], [1270, 1222, 1223], [1270, 1223, 1224], [1270, 1224, 1225], [1270, 1225, 1226], [1270, 1226, 1227], [1270, 1227, 1228], [1270, 1228, 1229], [1270, 1229, 1230], [1270, 1230, 1231], [1220, 1270, 1268], [1220, 1268, 1232], [1232, 1268, 1233], [1221, 1219, 1233], [1233, 1268, 1234], [1221, 1233, 1234], [1234, 1268, 1235], [1223, 1222, 1235], [1235, 1268, 1236], [1223, 1235, 1236], [1236, 1268, 1237], [1225, 1224, 1237], [1239, 1238, 1272], [1241, 1240, 1272], [1243, 1242, 1272], [1231, 1244, 1272], [1246, 1245, 1272], [1247, 1239, 1272], [1238, 1241, 1272], [1240, 1243, 1272], [1242, 1246, 1272], [1245, 1248, 1272], [1270, 1231, 1272], [1244, 1247, 1272], [1225, 1237, 1249], [1237, 1268, 1249], [1227, 1226, 1250], [1227, 1250, 1251], [1229, 1228, 1252], [1229, 1252, 1253], [1231, 1230, 1254], [1231, 1254, 1255], [1247, 1244, 1256], [1247, 1256, 1257], [1238, 1239, 1258], [1239, 1259, 1258], [1240, 1241, 1260], [1240, 1260, 1261], [1242, 1243, 1262], [1242, 1262, 1263], [1245, 1246, 1264], [1245, 1264, 1265], [1272, 1248, 1266], [1262, 1261, 1271], [1264, 1263, 1271], [1272, 1266, 1271], [1266, 1265, 1271], [1263, 1262, 1271], [1265, 1264, 1271], [1249, 1268, 1271], [1250, 1249, 1271], [1251, 1250, 1271], [1252, 1251, 1271], [1253, 1252, 1271], [1254, 1253, 1271], [1255, 1254, 1271], [1256, 1255, 1271], [1257, 1256, 1271], [1259, 1257, 1271], [1258, 1259, 1271], [1260, 1258, 1271], [1261, 1260, 1271], [1270, 1321, 1267], [1270, 1267, 1268], [1271, 1267, 1173], [1271, 1268, 1267], [1272, 1269, 1321], [1272, 1321, 1270], [1269, 1272, 1271], [1269, 1271, 1173], [1274, 1273, 1222], [1273, 1235, 1222], [1275, 1235, 1273], [1275, 1234, 1235], [1275, 1276, 1234], [1276, 1221, 1234], [1274, 1221, 1276], [1274, 1222, 1221], [1278, 1277, 1226], [1277, 1250, 1226], [1279, 1250, 1277], [1279, 1249, 1250], [1279, 1280, 1249], [1280, 1225, 1249], [1278, 1225, 1280], [1278, 1226, 1225], [1282, 1281, 1230], [1281, 1254, 1230], [1283, 1254, 1281], [1283, 1253, 1254], [1283, 1284, 1253], [1284, 1229, 1253], [1282, 1229, 1284], [1282, 1230, 1229], [1286, 1285, 1244], [1285, 1256, 1244], [1287, 1256, 1285], [1287, 1255, 1256], [1287, 1288, 1255], [1288, 1231, 1255], [1288, 1244, 1231], [1286, 1244, 1288], [1290, 1289, 1239], [1289, 1259, 1239], [1291, 1259, 1289], [1291, 1257, 1259], [1291, 1292, 1257], [1292, 1247, 1257], [1292, 1239, 1247], [1290, 1239, 1292], [1294, 1293, 1241], [1293, 1260, 1241], [1295, 1260, 1293], [1295, 1258, 1260], [1295, 1296, 1258], [1296, 1238, 1258], [1294, 1238, 1296], [1294, 1241, 1238], [1298, 1297, 1243], [1297, 1262, 1243], [1299, 1262, 1297], [1299, 1261, 1262], [1299, 1300, 1261], [1300, 1240, 1261], [1300, 1243, 1240], [1298, 1243, 1300], [1302, 1301, 1246], [1301, 1264, 1246], [1303, 1264, 1301], [1303, 1263, 1264], [1303, 1304, 1263], [1304, 1242, 1263], [1304, 1246, 1242], [1302, 1246, 1304], [1306, 1305, 1248], [1305, 1266, 1248], [1307, 1266, 1305], [1307, 1265, 1266], [1307, 1245, 1265], [1308, 1245, 1307], [1308, 1248, 1245], [1306, 1248, 1308], [1310, 1309, 1219], [1309, 1233, 1219], [1311, 1233, 1309], [1311, 1232, 1233], [1311, 1312, 1232], [1312, 1220, 1232], [1312, 1219, 1220], [1310, 1219, 1312], [1314, 1313, 1224], [1313, 1237, 1224], [1315, 1237, 1313], [1315, 1236, 1237], [1315, 1316, 1236], [1316, 1223, 1236], [1316, 1224, 1223], [1314, 1224, 1316], [1318, 1317, 1228], [1317, 1252, 1228], [1319, 1252, 1317], [1319, 1251, 1252], [1319, 1320, 1251], [1320, 1227, 1251], [1318, 1227, 1320], [1318, 1228, 1227], [1269, 1306, 1308], [1269, 1308, 1302], [1269, 1302, 1304], [1269, 1304, 1298], [1269, 1298, 1300], [1269, 1300, 1294], [1269, 1294, 1296], [1269, 1296, 1290], [1269, 1290, 1292], [1269, 1292, 1286], [1269, 1286, 1288], [1269, 1173, 1305], [1306, 1269, 1305], [1305, 1173, 1307], [1302, 1308, 1307], [1307, 1173, 1301], [1302, 1307, 1301], [1301, 1173, 1303], [1303, 1173, 1297], [1304, 1303, 1297], [1298, 1304, 1297], [1297, 1173, 1299], [1320, 1278, 1321], [1280, 1314, 1321], [1316, 1274, 1321], [1276, 1310, 1321], [1278, 1280, 1321], [1274, 1276, 1321], [1282, 1284, 1321], [1318, 1320, 1321], [1314, 1316, 1321], [1310, 1312, 1321], [1269, 1288, 1321], [1288, 1282, 1321], [1284, 1318, 1321], [1300, 1299, 1293], [1294, 1300, 1293], [1296, 1295, 1289], [1290, 1296, 1289], [1286, 1292, 1291], [1286, 1291, 1285], [1288, 1287, 1281], [1282, 1288, 1281], [1284, 1283, 1317], [1318, 1284, 1317], [1278, 1320, 1277], [1320, 1319, 1277], [1314, 1280, 1313], [1280, 1279, 1313], [1274, 1316, 1273], [1316, 1315, 1273], [1310, 1276, 1309], [1276, 1275, 1309], [1321, 1312, 1311], [1275, 1273, 1267], [1315, 1313, 1267], [1321, 1311, 1267], [1313, 1279, 1267], [1273, 1315, 1267], [1311, 1309, 1267], [1309, 1275, 1267], [1319, 1173, 1267], [1317, 1173, 1319], [1283, 1173, 1317], [1281, 1173, 1283], [1287, 1173, 1281], [1285, 1173, 1287], [1291, 1173, 1285], [1289, 1173, 1291], [1295, 1173, 1289], [1293, 1173, 1295], [1299, 1173, 1293], [1277, 1319, 1267], [1279, 1277, 1267], [1324, 1323, 1322], [1323, 1325, 1322], [1323, 1326, 1325], [1327, 1326, 1323], [1327, 1328, 1326], [1329, 1328, 1327], [1329, 1330, 1328], [1330, 1331, 1328], [1330, 1332, 1331], [1333, 1332, 1330], [1333, 1334, 1332], [1335, 1334, 1333], [1335, 1336, 1334], [1336, 1337, 1334], [1336, 1324, 1337], [1324, 1322, 1337], [1325, 1337, 1322], [1326, 1337, 1325], [1326, 1328, 1337], [1328, 1334, 1337], [1328, 1331, 1334], [1331, 1332, 1334], [1323, 1324, 1336], [1327, 1323, 1336], [1327, 1336, 1329], [1329, 1336, 1335], [1329, 1335, 1330], [1330, 1335, 1333]], "pts": [[2.609, 3.192, 0.282], [2.609, 3.173, 0.811], [2.609, 3.192, 0.811], [2.609, 3.173, 0.282], [3.094, 3.173, 0.811], [3.094, 3.192, 0.811], [2.709, 3.173, 0.338], [2.709, 3.173, 0.353], [2.709, 3.173, 0.383], [2.709, 3.173, 0.398], [2.709, 3.173, 0.428], [2.709, 3.173, 0.443], [2.709, 3.173, 0.473], [2.709, 3.173, 0.488], [2.709, 3.173, 0.518], [2.994, 3.173, 0.338], [2.994, 3.173, 0.353], [3.094, 3.173, 0.282], [2.994, 3.173, 0.383], [2.994, 3.173, 0.398], [2.709, 3.173, 0.665], [2.709, 3.173, 0.65], [2.709, 3.173, 0.575], [2.709, 3.173, 0.62], [2.709, 3.173, 0.605], [2.709, 3.173, 0.71], [2.709, 3.173, 0.695], [2.994, 3.173, 0.428], [2.994, 3.173, 0.443], [2.709, 3.173, 0.74], [2.709, 3.173, 0.755], [2.994, 3.173, 0.473], [2.994, 3.173, 0.488], [2.994, 3.173, 0.518], [2.994, 3.173, 0.575], [2.994, 3.173, 0.605], [2.994, 3.173, 0.62], [2.994, 3.173, 0.65], [2.994, 3.173, 0.665], [2.994, 3.173, 0.695], [2.994, 3.173, 0.71], [2.994, 3.173, 0.74], [2.994, 3.173, 0.755], [2.709, 3.192, 0.353], [2.709, 3.192, 0.338], [2.709, 3.192, 0.383], [2.709, 3.192, 0.398], [2.709, 3.192, 0.428], [2.709, 3.192, 0.443], [2.709, 3.192, 0.473], [2.709, 3.192, 0.488], [2.709, 3.192, 0.518], [2.994, 3.192, 0.338], [2.994, 3.192, 0.353], [3.094, 3.192, 0.282], [2.994, 3.192, 0.383], [2.994, 3.192, 0.398], [2.709, 3.192, 0.575], [2.709, 3.192, 0.62], [2.709, 3.192, 0.605], [2.709, 3.192, 0.665], [2.709, 3.192, 0.65], [2.709, 3.192, 0.71], [2.709, 3.192, 0.695], [2.994, 3.192, 0.428], [2.994, 3.192, 0.443], [2.709, 3.192, 0.74], [2.709, 3.192, 0.755], [2.994, 3.192, 0.473], [2.994, 3.192, 0.488], [2.994, 3.192, 0.518], [2.994, 3.192, 0.605], [2.994, 3.192, 0.575], [2.994, 3.192, 0.65], [2.994, 3.192, 0.62], [2.994, 3.192, 0.665], [2.994, 3.192, 0.695], [2.994, 3.192, 0.74], [2.994, 3.192, 0.71], [2.994, 3.192, 0.755], [2.588, 3.173, 0.925], [2.588, 3.716, 0.925], [2.588, 3.173, 0.906], [3.115, 3.173, 0.925], [3.115, 3.716, 0.925], [2.588, 3.173, 0.0], [2.607, 3.173, 0.906], [2.607, 3.173, 0.0], [2.588, 3.293, -0.0], [2.607, 3.293, -0.0], [2.588, 3.716, 0.906], [2.607, 3.716, 0.906], [2.588, 3.726, -0.0], [2.588, 3.626, -0.0], [2.588, 3.604364258, 0.17741247599999999], [2.588, 3.592258789, 0.276677063], [2.588, 3.580153564, 0.37594164999999996], [2.588, 3.293, 0.160060806], [2.588, 3.568048096, 0.475206238], [2.588, 3.293, 0.26], [2.588, 3.293, 0.359939209], [2.588, 3.293, 0.459878387], [2.588, 3.555942627, 0.574470825], [2.588, 3.5438371579999997, 0.673735413], [2.588, 3.5317316890000003, 0.773], [2.588, 3.293, 0.5598175660000001], [2.588, 3.293, 0.659756775], [2.588, 3.293, 0.773], [2.607, 3.626, -0.0], [2.607, 3.726, -0.0], [2.607, 3.716984863, 0.07392497299999999], [2.607, 3.604364258, 0.17741247599999999], [2.607, 3.592258789, 0.276677063], [2.607, 3.580153564, 0.37594164999999996], [2.607, 3.293, 0.160060806], [2.607, 3.568048096, 0.475206238], [2.607, 3.293, 0.26], [2.607, 3.293, 0.359939209], [2.607, 3.293, 0.459878387], [2.607, 3.555942627, 0.574470825], [2.607, 3.5438371579999997, 0.673735413], [2.607, 3.5317316890000003, 0.773], [2.607, 3.293, 0.5598175660000001], [2.607, 3.293, 0.659756775], [2.607, 3.293, 0.773], [2.588, 3.816, 0.086], [2.588, 3.716984863, 0.07392497299999999], [3.096, 3.173, 0.0], [3.115, 3.173, 0.0], [3.096, 3.293, -0.0], [3.096, 3.716, 0.906], [3.096, 3.726, -0.0], [3.096, 3.626, -0.0], [3.096, 3.716984863, 0.07392497299999999], [3.096, 3.604364258, 0.17741247599999999], [3.096, 3.816, 0.086], [3.096, 3.592258789, 0.276677063], [3.096, 3.580153564, 0.37594164999999996], [3.096, 3.293, 0.160060806], [3.096, 3.568048096, 0.475206238], [3.096, 3.293, 0.26], [3.096, 3.293, 0.359939209], [3.096, 3.293, 0.459878387], [3.096, 3.555942627, 0.574470825], [3.096, 3.5438371579999997, 0.673735413], [3.096, 3.5317316890000003, 0.773], [3.096, 3.293, 0.5598175660000001], [3.096, 3.293, 0.659756775], [3.096, 3.293, 0.773], [3.096, 3.173, 0.906], [3.115, 3.726, -0.0], [3.115, 3.604364258, 0.17741247599999999], [3.115, 3.568048096, 0.475206238], [3.115, 3.293, 0.359939209], [3.115, 3.293, 0.459878387], [3.115, 3.716, 0.906], [3.115, 3.555942627, 0.574470825], [3.115, 3.5438371579999997, 0.673735413], [3.115, 3.5317316890000003, 0.773], [3.115, 3.293, 0.5598175660000001], [3.115, 3.293, 0.659756775], [3.115, 3.173, 0.906], [3.115, 3.293, 0.773], [3.039, 3.277671143, 0.03949091], [3.039, 3.2677446289999996, 0.038280365000000004], [3.039, 3.307450684, 0.04312254], [3.039, 3.317376953, 0.044333084], [3.039, 3.347156494, 0.047964714], [3.039, 3.3570827640000003, 0.049175259], [3.039, 3.386862305, 0.052806888999999996], [3.039, 3.396788818, 0.054017433], [3.039, 3.4265681150000002, 0.057649067000000005], [3.039, 3.436494629, 0.058859608], [3.039, 3.466273926, 0.062491241], [3.039, 3.4762004390000003, 0.063701782], [3.039, 3.5059797359999996, 0.067333412], [2.664, 3.2677446289999996, 0.038280365000000004], [2.664, 3.277671143, 0.03949091], [2.664, 3.307450684, 0.04312254], [2.664, 3.317376953, 0.044333084], [2.664, 3.347156494, 0.047964714], [2.664, 3.3570827640000003, 0.049175259], [3.096, 3.8136999510000003, 0.104860275], [3.039, 3.585391357, 0.077017769], [3.039, 3.5556120609999997, 0.07338613100000001], [3.039, 3.625097168, 0.08185993999999999], [3.039, 3.595317871, 0.07822831], [3.039, 3.664802979, 0.086702118], [3.039, 3.635023682, 0.08307048], [3.039, 3.51590625, 0.068543961], [3.039, 3.704509033, 0.091544289], [3.039, 3.674729492, 0.087912659], [3.039, 3.545685547, 0.072175591], [3.039, 3.714435303, 0.09275483699999999], [2.664, 3.386862305, 0.052806888999999996], [2.664, 3.396788818, 0.054017433], [2.664, 3.4265681150000002, 0.057649067000000005], [2.664, 3.436494629, 0.058859608], [2.664, 3.466273926, 0.062491241], [2.664, 3.4762004390000003, 0.063701782], [2.664, 3.5059797359999996, 0.067333412], [2.664, 3.51590625, 0.068543961], [2.664, 3.545685547, 0.072175591], [2.664, 3.585391357, 0.077017769], [2.664, 3.5556120609999997, 0.07338613100000001], [2.664, 3.595317871, 0.07822831], [2.664, 3.625097168, 0.08185993999999999], [2.664, 3.635023682, 0.08307048], [2.664, 3.664802979, 0.086702118], [2.664, 3.674729492, 0.087912659], [2.664, 3.704509033, 0.091544289], [2.664, 3.714435303, 0.09275483699999999], [3.096, 3.1943000489999998, 0.010182931], [3.096, 3.192, 0.029043202999999997], [2.607, 3.192, 0.029043202999999997], [2.607, 3.1943000489999998, 0.010182931], [2.607, 3.8136999510000003, 0.104860275], [2.607, 3.816, 0.086], [2.664, 3.3196770019999997, 0.025472812999999997], [3.039, 3.3196770019999997, 0.025472812999999997], [2.664, 3.309750488, 0.024262268], [3.039, 3.309750488, 0.024262268], [2.664, 3.399088623, 0.035157162], [3.039, 3.399088623, 0.035157162], [2.664, 3.389162354, 0.033946617000000005], [3.039, 3.389162354, 0.033946617000000005], [2.664, 3.4785004880000003, 0.044841510999999994], [3.039, 3.4785004880000003, 0.044841510999999994], [2.664, 3.468573975, 0.04363097], [3.039, 3.468573975, 0.04363097], [2.664, 3.518206299, 0.049683689], [3.039, 3.518206299, 0.049683689], [2.664, 3.508279785, 0.048473145], [3.039, 3.508279785, 0.048473145], [2.664, 3.5579121089999997, 0.054525864], [3.039, 3.5579121089999997, 0.054525864], [2.664, 3.547985596, 0.053315319], [3.039, 3.547985596, 0.053315319], [2.664, 3.5976179200000002, 0.059368038], [3.039, 3.5976179200000002, 0.059368038], [2.664, 3.587691406, 0.058157494], [3.039, 3.587691406, 0.058157494], [2.664, 3.63732373, 0.064210213], [3.039, 3.63732373, 0.064210213], [2.664, 3.6273972170000004, 0.062999668], [3.039, 3.6273972170000004, 0.062999668], [2.664, 3.677029541, 0.069052391], [3.039, 3.677029541, 0.069052391], [2.664, 3.667103027, 0.067841843], [3.039, 3.667103027, 0.067841843], [2.664, 3.716735352, 0.073894562], [3.039, 3.716735352, 0.073894562], [2.664, 3.7068088379999997, 0.072684021], [3.039, 3.7068088379999997, 0.072684021], [2.664, 3.279971191, 0.020630638], [3.039, 3.279971191, 0.020630638], [2.664, 3.270044678, 0.019420094], [3.039, 3.270044678, 0.019420094], [2.664, 3.3593828119999998, 0.030314986999999998], [3.039, 3.3593828119999998, 0.030314986999999998], [2.664, 3.3494565429999996, 0.029104445], [3.039, 3.3494565429999996, 0.029104445], [2.664, 3.4387946780000003, 0.039999335999999996], [3.039, 3.4387946780000003, 0.039999335999999996], [2.664, 3.428868164, 0.038788795], [3.039, 3.428868164, 0.038788795], [3.136, 3.192, 0.282], [3.136, 3.173, 0.811], [3.136, 3.192, 0.811], [3.136, 3.173, 0.282], [3.621, 3.173, 0.811], [3.621, 3.173, 0.282], [3.236, 3.173, 0.338], [3.236, 3.173, 0.353], [3.236, 3.173, 0.383], [3.236, 3.173, 0.398], [3.236, 3.173, 0.428], [3.236, 3.173, 0.443], [3.236, 3.173, 0.473], [3.236, 3.173, 0.488], [3.236, 3.173, 0.518], [3.521, 3.173, 0.338], [3.521, 3.173, 0.353], [3.521, 3.173, 0.383], [3.521, 3.173, 0.398], [3.236, 3.173, 0.665], [3.236, 3.173, 0.65], [3.236, 3.173, 0.575], [3.236, 3.173, 0.62], [3.236, 3.173, 0.605], [3.236, 3.173, 0.71], [3.236, 3.173, 0.695], [3.521, 3.173, 0.428], [3.521, 3.173, 0.443], [3.236, 3.173, 0.74], [3.236, 3.173, 0.755], [3.521, 3.173, 0.473], [3.521, 3.173, 0.488], [3.521, 3.173, 0.518], [3.521, 3.173, 0.575], [3.521, 3.173, 0.605], [3.521, 3.173, 0.62], [3.521, 3.173, 0.65], [3.521, 3.173, 0.665], [3.521, 3.173, 0.695], [3.521, 3.173, 0.71], [3.521, 3.173, 0.74], [3.521, 3.173, 0.755], [3.236, 3.192, 0.353], [3.236, 3.192, 0.338], [3.236, 3.192, 0.383], [3.236, 3.192, 0.398], [3.236, 3.192, 0.428], [3.236, 3.192, 0.443], [3.236, 3.192, 0.473], [3.236, 3.192, 0.488], [3.236, 3.192, 0.518], [3.521, 3.192, 0.338], [3.521, 3.192, 0.353], [3.621, 3.192, 0.282], [3.521, 3.192, 0.383], [3.521, 3.192, 0.398], [3.236, 3.192, 0.575], [3.236, 3.192, 0.62], [3.236, 3.192, 0.605], [3.236, 3.192, 0.665], [3.236, 3.192, 0.65], [3.236, 3.192, 0.71], [3.236, 3.192, 0.695], [3.521, 3.192, 0.428], [3.521, 3.192, 0.443], [3.236, 3.192, 0.74], [3.236, 3.192, 0.755], [3.521, 3.192, 0.473], [3.521, 3.192, 0.488], [3.521, 3.192, 0.518], [3.521, 3.192, 0.605], [3.521, 3.192, 0.575], [3.521, 3.192, 0.65], [3.521, 3.192, 0.62], [3.521, 3.192, 0.665], [3.521, 3.192, 0.695], [3.521, 3.192, 0.74], [3.521, 3.192, 0.71], [3.521, 3.192, 0.755], [3.621, 3.192, 0.811], [3.642, 3.173, 0.925], [3.642, 3.716, 0.925], [3.134, 3.173, 0.0], [3.115, 3.293, -0.0], [3.134, 3.293, -0.0], [3.134, 3.716, 0.906], [3.115, 3.626, -0.0], [3.115, 3.592258789, 0.276677063], [3.115, 3.580153564, 0.37594164999999996], [3.115, 3.293, 0.160060806], [3.115, 3.293, 0.26], [3.134, 3.626, -0.0], [3.134, 3.604364258, 0.17741247599999999], [3.134, 3.592258789, 0.276677063], [3.134, 3.580153564, 0.37594164999999996], [3.134, 3.293, 0.160060806], [3.134, 3.568048096, 0.475206238], [3.134, 3.293, 0.26], [3.134, 3.293, 0.359939209], [3.134, 3.293, 0.459878387], [3.134, 3.555942627, 0.574470825], [3.134, 3.5438371579999997, 0.673735413], [3.134, 3.5317316890000003, 0.773], [3.134, 3.293, 0.5598175660000001], [3.134, 3.293, 0.659756775], [3.134, 3.173, 0.906], [3.134, 3.293, 0.773], [3.115, 3.816, 0.086], [3.115, 3.716984863, 0.07392497299999999], [3.134, 3.716984863, 0.07392497299999999], [3.134, 3.726, -0.0], [3.623, 3.173, 0.906], [3.623, 3.173, 0.0], [3.642, 3.173, 0.0], [3.623, 3.293, -0.0], [3.642, 3.716, 0.906], [3.623, 3.626, -0.0], [3.623, 3.604364258, 0.17741247599999999], [3.623, 3.592258789, 0.276677063], [3.623, 3.580153564, 0.37594164999999996], [3.623, 3.293, 0.160060806], [3.623, 3.568048096, 0.475206238], [3.623, 3.293, 0.26], [3.623, 3.293, 0.359939209], [3.623, 3.293, 0.459878387], [3.623, 3.555942627, 0.574470825], [3.623, 3.716, 0.906], [3.623, 3.5438371579999997, 0.673735413], [3.623, 3.5317316890000003, 0.773], [3.623, 3.293, 0.5598175660000001], [3.623, 3.293, 0.659756775], [3.623, 3.293, 0.773], [3.642, 3.726, -0.0], [3.642, 3.716984863, 0.07392497299999999], [3.642, 3.604364258, 0.17741247599999999], [3.642, 3.592258789, 0.276677063], [3.642, 3.580153564, 0.37594164999999996], [3.642, 3.293, 0.26], [3.642, 3.555942627, 0.574470825], [3.642, 3.5438371579999997, 0.673735413], [3.642, 3.5317316890000003, 0.773], [3.642, 3.293, 0.5598175660000001], [3.642, 3.293, 0.659756775], [3.642, 3.173, 0.906], [3.642, 3.293, 0.773], [3.623, 3.716984863, 0.07392497299999999], [3.623, 3.726, -0.0], [3.566, 3.277671143, 0.03949091], [3.566, 3.2677446289999996, 0.038280365000000004], [3.566, 3.307450684, 0.04312254], [3.566, 3.317376953, 0.044333084], [3.566, 3.347156494, 0.047964714], [3.566, 3.3570827640000003, 0.049175259], [3.566, 3.386862305, 0.052806888999999996], [3.566, 3.396788818, 0.054017433], [3.566, 3.4265681150000002, 0.057649067000000005], [3.566, 3.436494629, 0.058859608], [3.566, 3.466273926, 0.062491241], [3.566, 3.4762004390000003, 0.063701782], [3.566, 3.5059797359999996, 0.067333412], [3.134, 3.192, 0.029043202999999997], [3.191, 3.2677446289999996, 0.038280365000000004], [3.191, 3.277671143, 0.03949091], [3.191, 3.307450684, 0.04312254], [3.191, 3.317376953, 0.044333084], [3.191, 3.347156494, 0.047964714], [3.191, 3.3570827640000003, 0.049175259], [3.623, 3.8136999510000003, 0.104860275], [3.566, 3.585391357, 0.077017769], [3.566, 3.5556120609999997, 0.07338613100000001], [3.566, 3.625097168, 0.08185993999999999], [3.566, 3.595317871, 0.07822831], [3.566, 3.664802979, 0.086702118], [3.566, 3.635023682, 0.08307048], [3.566, 3.51590625, 0.068543961], [3.566, 3.704509033, 0.091544289], [3.566, 3.674729492, 0.087912659], [3.566, 3.545685547, 0.072175591], [3.566, 3.714435303, 0.09275483699999999], [3.191, 3.386862305, 0.052806888999999996], [3.191, 3.396788818, 0.054017433], [3.191, 3.4265681150000002, 0.057649067000000005], [3.191, 3.436494629, 0.058859608], [3.191, 3.466273926, 0.062491241], [3.191, 3.4762004390000003, 0.063701782], [3.191, 3.5059797359999996, 0.067333412], [3.191, 3.51590625, 0.068543961], [3.191, 3.545685547, 0.072175591], [3.191, 3.585391357, 0.077017769], [3.191, 3.5556120609999997, 0.07338613100000001], [3.191, 3.595317871, 0.07822831], [3.191, 3.625097168, 0.08185993999999999], [3.191, 3.635023682, 0.08307048], [3.191, 3.664802979, 0.086702118], [3.191, 3.674729492, 0.087912659], [3.191, 3.704509033, 0.091544289], [3.191, 3.714435303, 0.09275483699999999], [3.134, 3.1943000489999998, 0.010182931], [3.134, 3.816, 0.086], [3.134, 3.8136999510000003, 0.104860275], [3.623, 3.192, 0.029043202999999997], [3.191, 3.3196770019999997, 0.025472812999999997], [3.566, 3.3196770019999997, 0.025472812999999997], [3.191, 3.309750488, 0.024262268], [3.566, 3.309750488, 0.024262268], [3.191, 3.399088623, 0.035157162], [3.566, 3.399088623, 0.035157162], [3.191, 3.389162354, 0.033946617000000005], [3.566, 3.389162354, 0.033946617000000005], [3.191, 3.4785004880000003, 0.044841510999999994], [3.566, 3.4785004880000003, 0.044841510999999994], [3.191, 3.468573975, 0.04363097], [3.566, 3.468573975, 0.04363097], [3.191, 3.518206299, 0.049683689], [3.566, 3.518206299, 0.049683689], [3.191, 3.508279785, 0.048473145], [3.566, 3.508279785, 0.048473145], [3.191, 3.5579121089999997, 0.054525864], [3.566, 3.5579121089999997, 0.054525864], [3.191, 3.547985596, 0.053315319], [3.566, 3.547985596, 0.053315319], [3.191, 3.5976179200000002, 0.059368038], [3.566, 3.5976179200000002, 0.059368038], [3.191, 3.587691406, 0.058157494], [3.566, 3.587691406, 0.058157494], [3.191, 3.63732373, 0.064210213], [3.566, 3.63732373, 0.064210213], [3.191, 3.6273972170000004, 0.062999668], [3.566, 3.6273972170000004, 0.062999668], [3.191, 3.677029541, 0.069052391], [3.566, 3.677029541, 0.069052391], [3.191, 3.667103027, 0.067841843], [3.566, 3.667103027, 0.067841843], [3.191, 3.716735352, 0.073894562], [3.566, 3.716735352, 0.073894562], [3.191, 3.7068088379999997, 0.072684021], [3.566, 3.7068088379999997, 0.072684021], [3.191, 3.279971191, 0.020630638], [3.566, 3.279971191, 0.020630638], [3.191, 3.270044678, 0.019420094], [3.566, 3.270044678, 0.019420094], [3.191, 3.3593828119999998, 0.030314986999999998], [3.566, 3.3593828119999998, 0.030314986999999998], [3.191, 3.3494565429999996, 0.029104445], [3.566, 3.3494565429999996, 0.029104445], [3.191, 3.4387946780000003, 0.039999335999999996], [3.566, 3.4387946780000003, 0.039999335999999996], [3.191, 3.428868164, 0.038788795], [3.566, 3.428868164, 0.038788795], [3.623, 3.816, 0.086], [3.623, 3.1943000489999998, 0.010182931], [3.663, 3.192, 0.282], [3.663, 3.173, 0.811], [3.663, 3.192, 0.811], [3.663, 3.173, 0.282], [4.148, 3.173, 0.811], [4.148, 3.192, 0.811], [4.148, 3.173, 0.282], [4.148, 3.192, 0.282], [3.763, 3.173, 0.338], [3.763, 3.173, 0.353], [3.763, 3.173, 0.383], [3.763, 3.173, 0.398], [3.763, 3.173, 0.428], [3.763, 3.173, 0.443], [3.763, 3.173, 0.473], [3.763, 3.173, 0.488], [3.763, 3.173, 0.518], [4.048, 3.173, 0.338], [4.048, 3.173, 0.353], [4.048, 3.173, 0.383], [4.048, 3.173, 0.398], [3.763, 3.173, 0.665], [3.763, 3.173, 0.65], [3.763, 3.173, 0.575], [3.763, 3.173, 0.62], [3.763, 3.173, 0.605], [3.763, 3.173, 0.71], [3.763, 3.173, 0.695], [4.048, 3.173, 0.428], [4.048, 3.173, 0.443], [3.763, 3.173, 0.74], [3.763, 3.173, 0.755], [4.048, 3.173, 0.473], [4.048, 3.173, 0.488], [4.048, 3.173, 0.518], [4.048, 3.173, 0.575], [4.048, 3.173, 0.605], [4.048, 3.173, 0.62], [4.048, 3.173, 0.65], [4.048, 3.173, 0.665], [4.048, 3.173, 0.695], [4.048, 3.173, 0.71], [4.048, 3.173, 0.74], [4.048, 3.173, 0.755], [3.763, 3.192, 0.353], [3.763, 3.192, 0.338], [3.763, 3.192, 0.383], [3.763, 3.192, 0.398], [3.763, 3.192, 0.428], [3.763, 3.192, 0.443], [3.763, 3.192, 0.473], [3.763, 3.192, 0.488], [3.763, 3.192, 0.518], [4.048, 3.192, 0.338], [4.048, 3.192, 0.353], [4.048, 3.192, 0.383], [4.048, 3.192, 0.398], [3.763, 3.192, 0.575], [3.763, 3.192, 0.62], [3.763, 3.192, 0.605], [3.763, 3.192, 0.665], [3.763, 3.192, 0.65], [3.763, 3.192, 0.71], [3.763, 3.192, 0.695], [4.048, 3.192, 0.428], [4.048, 3.192, 0.443], [3.763, 3.192, 0.74], [3.763, 3.192, 0.755], [4.048, 3.192, 0.473], [4.048, 3.192, 0.488], [4.048, 3.192, 0.518], [4.048, 3.192, 0.605], [4.048, 3.192, 0.575], [4.048, 3.192, 0.65], [4.048, 3.192, 0.62], [4.048, 3.192, 0.665], [4.048, 3.192, 0.695], [4.048, 3.192, 0.74], [4.048, 3.192, 0.71], [4.048, 3.192, 0.755], [4.169, 3.173, 0.925], [4.169, 3.716, 0.925], [3.661, 3.173, 0.0], [3.642, 3.293, -0.0], [3.661, 3.293, -0.0], [3.642, 3.626, -0.0], [3.642, 3.293, 0.160060806], [3.642, 3.568048096, 0.475206238], [3.642, 3.293, 0.359939209], [3.642, 3.293, 0.459878387], [3.661, 3.626, -0.0], [3.661, 3.726, -0.0], [3.661, 3.604364258, 0.17741247599999999], [3.661, 3.592258789, 0.276677063], [3.661, 3.580153564, 0.37594164999999996], [3.661, 3.293, 0.160060806], [3.661, 3.568048096, 0.475206238], [3.661, 3.293, 0.26], [3.661, 3.293, 0.359939209], [3.661, 3.293, 0.459878387], [3.661, 3.716, 0.906], [3.661, 3.555942627, 0.574470825], [3.661, 3.5438371579999997, 0.673735413], [3.661, 3.5317316890000003, 0.773], [3.661, 3.293, 0.5598175660000001], [3.661, 3.293, 0.659756775], [3.661, 3.173, 0.906], [3.661, 3.293, 0.773], [3.642, 3.816, 0.086], [3.661, 3.816, 0.086], [3.661, 3.716984863, 0.07392497299999999], [4.15, 3.173, 0.0], [4.169, 3.173, 0.906], [4.169, 3.173, 0.0], [4.15, 3.293, -0.0], [4.169, 3.293, -0.0], [4.169, 3.716, 0.906], [4.15, 3.726, -0.0], [4.15, 3.626, -0.0], [4.15, 3.716984863, 0.07392497299999999], [4.15, 3.604364258, 0.17741247599999999], [4.15, 3.592258789, 0.276677063], [4.15, 3.580153564, 0.37594164999999996], [4.15, 3.293, 0.160060806], [4.15, 3.568048096, 0.475206238], [4.15, 3.293, 0.26], [4.15, 3.293, 0.359939209], [4.15, 3.293, 0.459878387], [4.15, 3.555942627, 0.574470825], [4.15, 3.716, 0.906], [4.15, 3.5438371579999997, 0.673735413], [4.15, 3.5317316890000003, 0.773], [4.15, 3.293, 0.5598175660000001], [4.15, 3.293, 0.659756775], [4.15, 3.293, 0.773], [4.15, 3.173, 0.906], [4.169, 3.626, -0.0], [4.169, 3.726, -0.0], [4.169, 3.716984863, 0.07392497299999999], [4.169, 3.604364258, 0.17741247599999999], [4.169, 3.816, 0.086], [4.169, 3.592258789, 0.276677063], [4.169, 3.580153564, 0.37594164999999996], [4.169, 3.293, 0.160060806], [4.169, 3.568048096, 0.475206238], [4.169, 3.293, 0.26], [4.169, 3.293, 0.359939209], [4.169, 3.293, 0.459878387], [4.169, 3.555942627, 0.574470825], [4.169, 3.5438371579999997, 0.673735413], [4.169, 3.5317316890000003, 0.773], [4.169, 3.293, 0.5598175660000001], [4.169, 3.293, 0.659756775], [4.169, 3.293, 0.773], [4.093, 3.277671143, 0.03949091], [4.093, 3.2677446289999996, 0.038280365000000004], [4.093, 3.307450684, 0.04312254], [4.093, 3.317376953, 0.044333084], [4.093, 3.347156494, 0.047964714], [4.093, 3.3570827640000003, 0.049175259], [4.093, 3.386862305, 0.052806888999999996], [4.093, 3.396788818, 0.054017433], [4.093, 3.4265681150000002, 0.057649067000000005], [4.093, 3.436494629, 0.058859608], [4.093, 3.466273926, 0.062491241], [4.093, 3.4762004390000003, 0.063701782], [4.093, 3.5059797359999996, 0.067333412], [3.661, 3.192, 0.029043202999999997], [3.718, 3.2677446289999996, 0.038280365000000004], [3.718, 3.277671143, 0.03949091], [3.718, 3.307450684, 0.04312254], [3.718, 3.317376953, 0.044333084], [3.718, 3.347156494, 0.047964714], [3.718, 3.3570827640000003, 0.049175259], [4.093, 3.585391357, 0.077017769], [4.093, 3.5556120609999997, 0.07338613100000001], [4.093, 3.625097168, 0.08185993999999999], [4.093, 3.595317871, 0.07822831], [4.093, 3.664802979, 0.086702118], [4.093, 3.635023682, 0.08307048], [4.093, 3.51590625, 0.068543961], [4.093, 3.704509033, 0.091544289], [4.093, 3.674729492, 0.087912659], [4.093, 3.545685547, 0.072175591], [4.093, 3.714435303, 0.09275483699999999], [3.718, 3.386862305, 0.052806888999999996], [3.718, 3.396788818, 0.054017433], [3.718, 3.4265681150000002, 0.057649067000000005], [3.718, 3.436494629, 0.058859608], [3.718, 3.466273926, 0.062491241], [3.718, 3.4762004390000003, 0.063701782], [3.718, 3.5059797359999996, 0.067333412], [3.718, 3.51590625, 0.068543961], [3.718, 3.545685547, 0.072175591], [3.718, 3.585391357, 0.077017769], [3.718, 3.5556120609999997, 0.07338613100000001], [3.718, 3.595317871, 0.07822831], [3.718, 3.625097168, 0.08185993999999999], [3.718, 3.635023682, 0.08307048], [3.718, 3.664802979, 0.086702118], [3.718, 3.674729492, 0.087912659], [3.718, 3.704509033, 0.091544289], [3.718, 3.714435303, 0.09275483699999999], [3.661, 3.8136999510000003, 0.104860275], [3.661, 3.1943000489999998, 0.010182931], [4.15, 3.1943000489999998, 0.010182931], [4.15, 3.192, 0.029043202999999997], [4.15, 3.8136999510000003, 0.104860275], [4.15, 3.816, 0.086], [3.718, 3.3196770019999997, 0.025472812999999997], [4.093, 3.3196770019999997, 0.025472812999999997], [3.718, 3.309750488, 0.024262268], [4.093, 3.309750488, 0.024262268], [3.718, 3.399088623, 0.035157162], [4.093, 3.399088623, 0.035157162], [3.718, 3.389162354, 0.033946617000000005], [4.093, 3.389162354, 0.033946617000000005], [3.718, 3.4785004880000003, 0.044841510999999994], [4.093, 3.4785004880000003, 0.044841510999999994], [3.718, 3.468573975, 0.04363097], [4.093, 3.468573975, 0.04363097], [3.718, 3.518206299, 0.049683689], [4.093, 3.518206299, 0.049683689], [3.718, 3.508279785, 0.048473145], [4.093, 3.508279785, 0.048473145], [3.718, 3.5579121089999997, 0.054525864], [4.093, 3.5579121089999997, 0.054525864], [3.718, 3.547985596, 0.053315319], [4.093, 3.547985596, 0.053315319], [3.718, 3.5976179200000002, 0.059368038], [4.093, 3.5976179200000002, 0.059368038], [3.718, 3.587691406, 0.058157494], [4.093, 3.587691406, 0.058157494], [3.718, 3.63732373, 0.064210213], [4.093, 3.63732373, 0.064210213], [3.718, 3.6273972170000004, 0.062999668], [4.093, 3.6273972170000004, 0.062999668], [3.718, 3.677029541, 0.069052391], [4.093, 3.677029541, 0.069052391], [3.718, 3.667103027, 0.067841843], [4.093, 3.667103027, 0.067841843], [3.718, 3.716735352, 0.073894562], [4.093, 3.716735352, 0.073894562], [3.718, 3.7068088379999997, 0.072684021], [4.093, 3.7068088379999997, 0.072684021], [3.718, 3.279971191, 0.020630638], [4.093, 3.279971191, 0.020630638], [3.718, 3.270044678, 0.019420094], [4.093, 3.270044678, 0.019420094], [3.718, 3.3593828119999998, 0.030314986999999998], [4.093, 3.3593828119999998, 0.030314986999999998], [3.718, 3.3494565429999996, 0.029104445], [4.093, 3.3494565429999996, 0.029104445], [3.718, 3.4387946780000003, 0.039999335999999996], [4.093, 3.4387946780000003, 0.039999335999999996], [3.718, 3.428868164, 0.038788795], [4.093, 3.428868164, 0.038788795], [4.343381836, 3.2063579100000004, 0.282], [4.346681152, 3.187646729, 0.811], [4.343381836, 3.2063579100000004, 0.811], [4.346681152, 3.187646729, 0.282], [4.8243125, 3.271865967, 0.811], [4.821013184000001, 3.290577393, 0.811], [4.8243125, 3.271865967, 0.282], [4.821013184000001, 3.290577393, 0.282], [4.4451616210000005, 3.2050114749999996, 0.338], [4.4451616210000005, 3.2050114749999996, 0.353], [4.4451616210000005, 3.2050114749999996, 0.383], [4.4451616210000005, 3.2050114749999996, 0.398], [4.4451616210000005, 3.2050114749999996, 0.428], [4.4451616210000005, 3.2050114749999996, 0.443], [4.4451616210000005, 3.2050114749999996, 0.473], [4.4451616210000005, 3.2050114749999996, 0.488], [4.4451616210000005, 3.2050114749999996, 0.518], [4.7258320309999995, 3.254501221, 0.338], [4.7258320309999995, 3.254501221, 0.353], [4.7258320309999995, 3.254501221, 0.383], [4.7258320309999995, 3.254501221, 0.398], [4.4451616210000005, 3.2050114749999996, 0.665], [4.4451616210000005, 3.2050114749999996, 0.65], [4.4451616210000005, 3.2050114749999996, 0.575], [4.4451616210000005, 3.2050114749999996, 0.62], [4.4451616210000005, 3.2050114749999996, 0.605], [4.4451616210000005, 3.2050114749999996, 0.71], [4.4451616210000005, 3.2050114749999996, 0.695], [4.7258320309999995, 3.254501221, 0.428], [4.7258320309999995, 3.254501221, 0.443], [4.4451616210000005, 3.2050114749999996, 0.74], [4.4451616210000005, 3.2050114749999996, 0.755], [4.7258320309999995, 3.254501221, 0.473], [4.7258320309999995, 3.254501221, 0.488], [4.7258320309999995, 3.254501221, 0.518], [4.7258320309999995, 3.254501221, 0.575], [4.7258320309999995, 3.254501221, 0.605], [4.7258320309999995, 3.254501221, 0.62], [4.7258320309999995, 3.254501221, 0.65], [4.7258320309999995, 3.254501221, 0.665], [4.7258320309999995, 3.254501221, 0.695], [4.7258320309999995, 3.254501221, 0.71], [4.7258320309999995, 3.254501221, 0.74], [4.7258320309999995, 3.254501221, 0.755], [4.441862305, 3.223722656, 0.353], [4.441862305, 3.223722656, 0.338], [4.441862305, 3.223722656, 0.383], [4.441862305, 3.223722656, 0.398], [4.441862305, 3.223722656, 0.428], [4.441862305, 3.223722656, 0.443], [4.441862305, 3.223722656, 0.473], [4.441862305, 3.223722656, 0.488], [4.441862305, 3.223722656, 0.518], [4.722532715000001, 3.273212402, 0.338], [4.722532715000001, 3.273212402, 0.353], [4.722532715000001, 3.273212402, 0.383], [4.722532715000001, 3.273212402, 0.398], [4.441862305, 3.223722656, 0.575], [4.441862305, 3.223722656, 0.62], [4.441862305, 3.223722656, 0.605], [4.441862305, 3.223722656, 0.665], [4.441862305, 3.223722656, 0.65], [4.441862305, 3.223722656, 0.71], [4.441862305, 3.223722656, 0.695], [4.722532715000001, 3.273212402, 0.428], [4.722532715000001, 3.273212402, 0.443], [4.441862305, 3.223722656, 0.74], [4.441862305, 3.223722656, 0.755], [4.722532715000001, 3.273212402, 0.473], [4.722532715000001, 3.273212402, 0.488], [4.722532715000001, 3.273212402, 0.518], [4.722532715000001, 3.273212402, 0.605], [4.722532715000001, 3.273212402, 0.575], [4.722532715000001, 3.273212402, 0.65], [4.722532715000001, 3.273212402, 0.62], [4.722532715000001, 3.273212402, 0.665], [4.722532715000001, 3.273212402, 0.695], [4.722532715000001, 3.273212402, 0.74], [4.722532715000001, 3.273212402, 0.71], [4.722532715000001, 3.273212402, 0.755], [4.326, 3.184, 0.925], [4.231708984, 3.718750488, 0.925], [4.326, 3.184, 0.906], [4.844993652, 3.2755126949999998, 0.906], [4.844993652, 3.2755126949999998, 0.925], [4.750702637000001, 3.810263184, 0.925], [4.326, 3.184, 0.0], [4.344711426, 3.1872993160000003, 0.906], [4.344711426, 3.1872993160000003, 0.0], [4.305162109, 3.302177002, -0.0], [4.323873535, 3.305476318, -0.0], [4.250420409999999, 3.722049805, 0.906], [4.247337402, 3.63011792, -0.0], [4.231538086, 3.719720459, 0.07392497299999999], [4.251094237999999, 3.608811035, 0.17741247599999999], [4.214344238, 3.817231445, 0.086], [4.253196289000001, 3.596889404, 0.276677063], [4.25529834, 3.584967773, 0.37594164999999996], [4.305162109, 3.302177002, 0.160060806], [4.257400391, 3.5730463869999998, 0.475206238], [4.305162109, 3.302177002, 0.26], [4.305162109, 3.302177002, 0.359939209], [4.305162109, 3.302177002, 0.459878387], [4.25950293, 3.5611247560000003, 0.574470825], [4.231708984, 3.718750488, 0.906], [4.26160498, 3.5492033689999998, 0.673735413], [4.263707031, 3.5372817380000003, 0.773], [4.305162109, 3.302177002, 0.5598175660000001], [4.305162109, 3.302177002, 0.659756775], [4.305162109, 3.302177002, 0.773], [4.266048828, 3.6334172359999997, -0.0], [4.269805664000001, 3.612110352, 0.17741247599999999], [4.233055664, 3.8205307619999997, 0.086], [4.271907715, 3.600188721, 0.276677063], [4.274009766, 3.58826709, 0.37594164999999996], [4.323873535, 3.305476318, 0.160060806], [4.276111815999999, 3.576345703, 0.475206238], [4.323873535, 3.305476318, 0.26], [4.323873535, 3.305476318, 0.359939209], [4.323873535, 3.305476318, 0.459878387], [4.278213867000001, 3.564424072, 0.574470825], [4.280315918, 3.552502686, 0.673735413], [4.2824184569999995, 3.540581055, 0.773], [4.323873535, 3.305476318, 0.5598175660000001], [4.323873535, 3.305476318, 0.659756775], [4.323873535, 3.305476318, 0.773], [4.250249512, 3.723019775, 0.07392497299999999], [4.229972656, 3.7285986330000003, -0.0], [4.248684082, 3.731897949, -0.0], [4.826282227, 3.2722133789999996, 0.0], [4.844993652, 3.2755126949999998, 0.0], [4.805444336, 3.390390137, -0.0], [4.824155762, 3.393689453, -0.0], [4.750702637000001, 3.810263184, 0.906], [4.730254883, 3.8168120119999998, -0.0], [4.747619629, 3.718331299, -0.0], [4.731820312, 3.807933594, 0.07392497299999999], [4.751376465, 3.69702417, 0.17741247599999999], [4.714626465, 3.9054445799999997, 0.086], [4.753479004, 3.6851027829999996, 0.276677063], [4.7555810549999995, 3.673181152, 0.37594164999999996], [4.805444336, 3.390390137, 0.160060806], [4.757683105, 3.661259521, 0.475206238], [4.805444336, 3.390390137, 0.26], [4.805444336, 3.390390137, 0.359939209], [4.805444336, 3.390390137, 0.459878387], [4.7597851559999995, 3.649338135, 0.574470825], [4.7319912109999995, 3.806963867, 0.906], [4.761887207, 3.637416504, 0.673735413], [4.763989258, 3.625495117, 0.773], [4.805444336, 3.390390137, 0.5598175660000001], [4.805444336, 3.390390137, 0.659756775], [4.805444336, 3.390390137, 0.773], [4.826282227, 3.2722133789999996, 0.906], [4.766331054999999, 3.721630615, -0.0], [4.750531737999999, 3.81123291, 0.07392497299999999], [4.770087891, 3.700323486, 0.17741247599999999], [4.733337891, 3.908743896, 0.086], [4.772189941, 3.6884021, 0.276677063], [4.774291992, 3.676480469, 0.37594164999999996], [4.824155762, 3.393689453, 0.160060806], [4.776394531, 3.664558838, 0.475206238], [4.824155762, 3.393689453, 0.26], [4.824155762, 3.393689453, 0.359939209], [4.824155762, 3.393689453, 0.459878387], [4.778496582, 3.652637451, 0.574470825], [4.780598632999999, 3.64071582, 0.673735413], [4.782700684, 3.628794434, 0.773], [4.824155762, 3.393689453, 0.5598175660000001], [4.824155762, 3.393689453, 0.659756775], [4.824155762, 3.393689453, 0.773], [4.748966309, 3.820111328, -0.0], [4.751972168, 3.36539624, 0.03949091], [4.7536962890000005, 3.355620605, 0.038280365000000004], [4.74680127, 3.394723389, 0.04312254], [4.7450776370000005, 3.404499023, 0.044333084], [4.73990625, 3.433825928, 0.047964714], [4.7381826170000005, 3.443601562, 0.049175259], [4.733011719, 3.472928467, 0.052806888999999996], [4.731287598, 3.482704102, 0.054017433], [4.726116699, 3.51203125, 0.057649067000000005], [4.724393065999999, 3.521806885, 0.058859608], [4.7192216799999995, 3.551133789, 0.062491241], [4.717498047, 3.560909424, 0.063701782], [4.712327148, 3.590236328, 0.067333412], [4.341412109, 3.206010742, 0.029043202999999997], [4.3843930659999995, 3.290502686, 0.038280365000000004], [4.382669434, 3.30027832, 0.03949091], [4.377498535, 3.329605225, 0.04312254], [4.375774414, 3.339380859, 0.044333084], [4.370603516, 3.3687077640000003, 0.047964714], [4.368879883, 3.3784833979999997, 0.049175259], [4.715025879, 3.9031796880000003, 0.104860275], [4.698537109, 3.66844165, 0.077017769], [4.703708496, 3.639114746, 0.07338613100000001], [4.691642578, 3.707544189, 0.08185993999999999], [4.696813477, 3.678217285, 0.07822831], [4.684747559000001, 3.746646729, 0.086702118], [4.6899189450000005, 3.717319824, 0.08307048], [4.710603027, 3.600011963, 0.068543961], [4.677852539000001, 3.7857495119999998, 0.091544289], [4.683023926, 3.756422363, 0.087912659], [4.705432129, 3.629338867, 0.072175591], [4.676128906, 3.795525146, 0.09275483699999999], [4.363708496, 3.407810547, 0.052806888999999996], [4.361984863, 3.417586182, 0.054017433], [4.356813965000001, 3.446913086, 0.057649067000000005], [4.355089844, 3.456688721, 0.058859608], [4.349918945000001, 3.486015625, 0.062491241], [4.348195312, 3.49579126, 0.063701782], [4.343023926, 3.525118408, 0.067333412], [4.341300293000001, 3.534894043, 0.068543961], [4.3361293949999995, 3.564220947, 0.072175591], [4.329234375, 3.603323486, 0.077017769], [4.334405273000001, 3.5739965820000004, 0.07338613100000001], [4.327510742, 3.6130991210000003, 0.07822831], [4.322339355, 3.6424260250000002, 0.08185993999999999], [4.3206157229999995, 3.652201904, 0.08307048], [4.315444824, 3.681528809, 0.086702118], [4.313721191, 3.691304443, 0.087912659], [4.308549804999999, 3.720631348, 0.091544289], [4.306826172, 3.730406982, 0.09275483699999999], [4.2334550779999995, 3.818265625, 0.104860275], [4.822583496, 3.293189697, 0.010182931], [4.341012695000001, 3.208275635, 0.010182931], [4.8229829099999995, 3.2909245609999997, 0.029043202999999997], [4.375375, 3.341645996, 0.025472812999999997], [4.744678223, 3.406763916, 0.025472812999999997], [4.3770991210000005, 3.3318703609999996, 0.024262268], [4.746401855, 3.396988281, 0.024262268], [4.361585449000001, 3.419851318, 0.035157162], [4.730888184, 3.484969238, 0.035157162], [4.363309082, 3.4100754390000003, 0.033946617000000005], [4.732612305, 3.475193604, 0.033946617000000005], [4.347795898, 3.498056396, 0.044841510999999994], [4.717098633, 3.563174561, 0.044841510999999994], [4.349519531, 3.488280762, 0.04363097], [4.718822266, 3.553398926, 0.04363097], [4.340900878999999, 3.537158936, 0.049683689], [4.710204102, 3.6022771, 0.049683689], [4.342624512, 3.527383301, 0.048473145], [4.711927734, 3.592501465, 0.048473145], [4.334006348, 3.576261719, 0.054525864], [4.703309082, 3.641379639, 0.054525864], [4.33572998, 3.566486084, 0.053315319], [4.705032715000001, 3.6316040039999997, 0.053315319], [4.327111328, 3.615364258, 0.059368038], [4.696414062, 3.680482422, 0.059368038], [4.328834960999999, 3.605588623, 0.058157494], [4.698137695000001, 3.670706787, 0.058157494], [4.320216309, 3.654466797, 0.064210213], [4.689519531, 3.719584961, 0.064210213], [4.321939940999999, 3.644691162, 0.062999668], [4.691243164, 3.7098093260000002, 0.062999668], [4.3133217770000005, 3.6935695799999997, 0.069052391], [4.682624512, 3.7586875, 0.069052391], [4.31504541, 3.6837939449999997, 0.067841843], [4.6843481449999995, 3.748911865, 0.067841843], [4.306426758, 3.7326721189999996, 0.073894562], [4.675729492, 3.797790283, 0.073894562], [4.308150391, 3.722896484, 0.072684021], [4.677453125, 3.788014404, 0.072684021], [4.38227002, 3.302543457, 0.020630638], [4.751572754, 3.367661377, 0.020630638], [4.383993652, 3.2927678219999996, 0.019420094], [4.753296875, 3.357885742, 0.019420094], [4.368480469, 3.3807485350000004, 0.030314986999999998], [4.737783203, 3.445866699, 0.030314986999999998], [4.370204102, 3.3709729000000004, 0.029104445], [4.7395068359999994, 3.436091064, 0.029104445], [4.35469043, 3.458953857, 0.039999335999999996], [4.723993652, 3.524071777, 0.039999335999999996], [4.356414550999999, 3.449178223, 0.038788795], [4.725717285, 3.514296143, 0.038788795], [1.932980225, 3.292064697, 0.282], [1.9296809080000001, 3.273353271, 0.811], [1.932980225, 3.292064697, 0.811], [1.9296809080000001, 3.273353271, 0.282], [2.407312744, 3.1891340329999998, 0.811], [2.407312744, 3.1891340329999998, 0.282], [2.4106120609999997, 3.207845459, 0.282], [2.028161743, 3.2559885250000002, 0.338], [2.028161743, 3.2559885250000002, 0.353], [2.028161743, 3.2559885250000002, 0.383], [2.028161743, 3.2559885250000002, 0.398], [2.028161743, 3.2559885250000002, 0.428], [2.028161743, 3.2559885250000002, 0.443], [2.028161743, 3.2559885250000002, 0.473], [2.028161743, 3.2559885250000002, 0.488], [2.028161743, 3.2559885250000002, 0.518], [2.308832031, 3.206498779, 0.338], [2.308832031, 3.206498779, 0.353], [2.308832031, 3.206498779, 0.383], [2.308832031, 3.206498779, 0.398], [2.028161743, 3.2559885250000002, 0.665], [2.028161743, 3.2559885250000002, 0.65], [2.028161743, 3.2559885250000002, 0.575], [2.028161743, 3.2559885250000002, 0.62], [2.028161743, 3.2559885250000002, 0.605], [2.028161743, 3.2559885250000002, 0.71], [2.028161743, 3.2559885250000002, 0.695], [2.308832031, 3.206498779, 0.428], [2.308832031, 3.206498779, 0.443], [2.028161743, 3.2559885250000002, 0.74], [2.028161743, 3.2559885250000002, 0.755], [2.308832031, 3.206498779, 0.473], [2.308832031, 3.206498779, 0.488], [2.308832031, 3.206498779, 0.518], [2.308832031, 3.206498779, 0.575], [2.308832031, 3.206498779, 0.605], [2.308832031, 3.206498779, 0.62], [2.308832031, 3.206498779, 0.65], [2.308832031, 3.206498779, 0.665], [2.308832031, 3.206498779, 0.695], [2.308832031, 3.206498779, 0.71], [2.308832031, 3.206498779, 0.74], [2.308832031, 3.206498779, 0.755], [2.0314610600000003, 3.274699951, 0.353], [2.0314610600000003, 3.274699951, 0.338], [2.0314610600000003, 3.274699951, 0.383], [2.0314610600000003, 3.274699951, 0.398], [2.0314610600000003, 3.274699951, 0.428], [2.0314610600000003, 3.274699951, 0.443], [2.0314610600000003, 3.274699951, 0.473], [2.0314610600000003, 3.274699951, 0.488], [2.0314610600000003, 3.274699951, 0.518], [2.312131348, 3.2252102049999998, 0.338], [2.312131348, 3.2252102049999998, 0.353], [2.312131348, 3.2252102049999998, 0.383], [2.312131348, 3.2252102049999998, 0.398], [2.0314610600000003, 3.274699951, 0.575], [2.0314610600000003, 3.274699951, 0.62], [2.0314610600000003, 3.274699951, 0.605], [2.0314610600000003, 3.274699951, 0.665], [2.0314610600000003, 3.274699951, 0.65], [2.0314610600000003, 3.274699951, 0.71], [2.0314610600000003, 3.274699951, 0.695], [2.312131348, 3.2252102049999998, 0.428], [2.312131348, 3.2252102049999998, 0.443], [2.0314610600000003, 3.274699951, 0.74], [2.0314610600000003, 3.274699951, 0.755], [2.312131348, 3.2252102049999998, 0.473], [2.312131348, 3.2252102049999998, 0.488], [2.312131348, 3.2252102049999998, 0.518], [2.312131348, 3.2252102049999998, 0.605], [2.312131348, 3.2252102049999998, 0.575], [2.312131348, 3.2252102049999998, 0.65], [2.312131348, 3.2252102049999998, 0.62], [2.312131348, 3.2252102049999998, 0.665], [2.312131348, 3.2252102049999998, 0.695], [2.312131348, 3.2252102049999998, 0.74], [2.312131348, 3.2252102049999998, 0.71], [2.312131348, 3.2252102049999998, 0.755], [2.4106120609999997, 3.207845459, 0.811], [1.909, 3.277, 0.925], [2.003291016, 3.811750488, 0.925], [1.909, 3.277, 0.906], [2.427993652, 3.185487305, 0.925], [2.5222846679999997, 3.720238037, 0.925], [1.909, 3.277, 0.0], [1.927711304, 3.273700684, 0.0], [1.929837769, 3.395177002, -0.0], [1.948549072, 3.391877686, -0.0], [1.9876625979999998, 3.72311792, -0.0], [2.003461914, 3.8127204590000003, 0.07392497299999999], [1.98390564, 3.701811035, 0.17741247599999999], [1.981803589, 3.689889404, 0.276677063], [1.9797014160000002, 3.6779677729999998, 0.37594164999999996], [1.929837769, 3.395177002, 0.160060806], [1.977599365, 3.6660463869999997, 0.475206238], [1.929837769, 3.395177002, 0.26], [1.929837769, 3.395177002, 0.359939209], [1.929837769, 3.395177002, 0.459878387], [1.975497314, 3.6541247560000003, 0.574470825], [2.003291016, 3.811750488, 0.906], [1.973395142, 3.6422033689999997, 0.673735413], [1.971293091, 3.6302817380000003, 0.773], [1.929837769, 3.395177002, 0.5598175660000001], [1.929837769, 3.395177002, 0.659756775], [1.929837769, 3.395177002, 0.773], [2.0063740230000002, 3.719818604, -0.0], [2.02373877, 3.818299316, -0.0], [2.002616943, 3.698511719, 0.17741247599999999], [2.000514893, 3.686590088, 0.276677063], [1.998412842, 3.674668457, 0.37594164999999996], [1.948549072, 3.391877686, 0.160060806], [1.9963106689999999, 3.66274707, 0.475206238], [1.948549072, 3.391877686, 0.26], [1.948549072, 3.391877686, 0.359939209], [1.948549072, 3.391877686, 0.459878387], [2.022002319, 3.808451416, 0.906], [1.9942086179999998, 3.650825439, 0.574470825], [1.992106567, 3.638904053, 0.673735413], [1.990004395, 3.626982422, 0.773], [1.948549072, 3.391877686, 0.5598175660000001], [1.948549072, 3.391877686, 0.659756775], [1.927711304, 3.273700684, 0.906], [1.948549072, 3.391877686, 0.773], [2.020655762, 3.910231445, 0.086], [2.039367065, 3.906932129, 0.086], [2.02217334, 3.8094211430000002, 0.07392497299999999], [2.005027466, 3.8215986330000002, -0.0], [2.4092822270000003, 3.188786621, 0.0], [2.427993652, 3.185487305, 0.0], [2.430120117, 3.306963623, -0.0], [2.448831543, 3.303664307, -0.0], [2.503573242, 3.723537354, 0.906], [2.5222846679999997, 3.720238037, 0.906], [2.487945068, 3.634904541, -0.0], [2.503744385, 3.72450708, 0.07392497299999999], [2.484187988, 3.613597656, 0.17741247599999999], [2.482085938, 3.601676025, 0.276677063], [2.479983887, 3.589754639, 0.37594164999999996], [2.430120117, 3.306963623, 0.160060806], [2.477881592, 3.5778330080000003, 0.475206238], [2.430120117, 3.306963623, 0.26], [2.430120117, 3.306963623, 0.359939209], [2.430120117, 3.306963623, 0.459878387], [2.475779541, 3.565911621, 0.574470825], [2.47367749, 3.55398999, 0.673735413], [2.471575439, 3.5420686040000002, 0.773], [2.430120117, 3.306963623, 0.5598175660000001], [2.430120117, 3.306963623, 0.659756775], [2.430120117, 3.306963623, 0.773], [2.4092822270000003, 3.188786621, 0.906], [2.50665625, 3.631605225, -0.0], [2.52402124, 3.730086182, -0.0], [2.522455566, 3.7212077640000003, 0.07392497299999999], [2.502899414, 3.61029834, 0.17741247599999999], [2.539649414, 3.81871875, 0.086], [2.500797119, 3.598376709, 0.276677063], [2.498695068, 3.586455322, 0.37594164999999996], [2.448831543, 3.303664307, 0.160060806], [2.496593018, 3.574533691, 0.475206238], [2.448831543, 3.303664307, 0.26], [2.448831543, 3.303664307, 0.359939209], [2.448831543, 3.303664307, 0.459878387], [2.4944909670000004, 3.562612305, 0.574470825], [2.492388916, 3.550690674, 0.673735413], [2.490286865, 3.538769287, 0.773], [2.448831543, 3.303664307, 0.5598175660000001], [2.448831543, 3.303664307, 0.659756775], [2.427993652, 3.185487305, 0.906], [2.448831543, 3.303664307, 0.773], [2.5053098140000003, 3.733385498, -0.0], [2.371324219, 3.301765625, 0.03949091], [2.3696005860000002, 3.2919899900000003, 0.038280365000000004], [2.376495361, 3.331092529, 0.04312254], [2.378218994, 3.340868408, 0.044333084], [2.3833901369999997, 3.370195312, 0.047964714], [2.385114014, 3.379970947, 0.049175259], [2.390285156, 3.4092978520000004, 0.052806888999999996], [2.392008789, 3.419073486, 0.054017433], [2.397179932, 3.4484003910000003, 0.057649067000000005], [2.3989035640000003, 3.458176025, 0.058859608], [2.4040747070000004, 3.487503174, 0.062491241], [2.4057985840000002, 3.497278809, 0.063701782], [2.4109697270000003, 3.526605713, 0.067333412], [2.000297607, 3.357108154, 0.038280365000000004], [2.002021362, 3.366883789, 0.03949091], [2.007192505, 3.396210693, 0.04312254], [2.008916138, 3.405986328, 0.044333084], [2.01408728, 3.435313232, 0.047964714], [2.015811035, 3.445088867, 0.049175259], [2.424759277, 3.604811035, 0.077017769], [2.419588135, 3.575483887, 0.07338613100000001], [2.4316540530000004, 3.643913574, 0.08185993999999999], [2.4264829100000003, 3.61458667, 0.07822831], [2.438549072, 3.6830161130000003, 0.086702118], [2.4333779300000002, 3.653689209, 0.08307048], [2.412693359, 3.536381348, 0.068543961], [2.445443848, 3.722118896, 0.091544289], [2.440272705, 3.6927917480000003, 0.087912659], [2.417864502, 3.565708252, 0.072175591], [2.44716748, 3.731894531, 0.09275483699999999], [2.020982178, 3.474416016, 0.052806888999999996], [2.022705933, 3.48419165, 0.054017433], [2.027877075, 3.513518555, 0.057649067000000005], [2.029600708, 3.523294189, 0.058859608], [2.034771851, 3.552621094, 0.062491241], [2.0364956050000003, 3.562396729, 0.063701782], [2.041666748, 3.5917238769999997, 0.067333412], [2.043390381, 3.6014995119999997, 0.068543961], [2.048561523, 3.6308264159999997, 0.072175591], [2.055456299, 3.669928955, 0.077017769], [2.0502851559999997, 3.6406020509999997, 0.07338613100000001], [2.0571801759999997, 3.6797045899999996, 0.07822831], [2.062351318, 3.709031494, 0.08185993999999999], [2.0640749510000003, 3.718807373, 0.08307048], [2.069246094, 3.748134277, 0.086702118], [2.070969727, 3.757909912, 0.087912659], [2.076140869, 3.787236816, 0.091544289], [2.0778647460000004, 3.797012451, 0.09275483699999999], [1.931410034, 3.294677002, 0.010182931], [1.9310106200000001, 3.292412109, 0.029043202999999997], [2.520938232, 3.822018066, 0.086], [2.412581543, 3.207498047, 0.029043202999999997], [2.038967773, 3.904666992, 0.104860275], [2.520538818, 3.81975293, 0.104860275], [2.009315552, 3.4082514649999998, 0.025472812999999997], [2.378618408, 3.343133301, 0.025472812999999997], [2.007591919, 3.3984758299999998, 0.024262268], [2.3768947750000002, 3.333357666, 0.024262268], [2.023105225, 3.4864567870000003, 0.035157162], [2.392408203, 3.421338623, 0.035157162], [2.021381592, 3.4766809079999996, 0.033946617000000005], [2.39068457, 3.411562988, 0.033946617000000005], [2.03689502, 3.564661865, 0.044841510999999994], [2.406197998, 3.499543945, 0.044841510999999994], [2.035171265, 3.55488623, 0.04363097], [2.4044741210000002, 3.489768066, 0.04363097], [2.043789795, 3.603764404, 0.049683689], [2.413092773, 3.538646484, 0.049683689], [2.0420661620000002, 3.5939887699999997, 0.048473145], [2.411369141, 3.5288708499999997, 0.048473145], [2.05068457, 3.6428671880000003, 0.054525864], [2.419987549, 3.577749023, 0.054525864], [2.048960938, 3.6330915530000003, 0.053315319], [2.418263916, 3.567973389, 0.053315319], [2.05757959, 3.6819697270000002, 0.059368038], [2.426882324, 3.616851562, 0.059368038], [2.055855713, 3.6721940920000002, 0.058157494], [2.425158691, 3.607075928, 0.058157494], [2.064474365, 3.721072266, 0.064210213], [2.433777344, 3.655954346, 0.064210213], [2.062750732, 3.711296631, 0.062999668], [2.4320534670000002, 3.646178711, 0.062999668], [2.071369141, 3.760175049, 0.069052391], [2.440672119, 3.695056885, 0.069052391], [2.0696455080000002, 3.750399414, 0.067841843], [2.4389484859999997, 3.68528125, 0.067841843], [2.0782641600000002, 3.799277588, 0.073894562], [2.447566895, 3.734159424, 0.073894562], [2.076540283, 3.789501953, 0.072684021], [2.445843262, 3.724383789, 0.072684021], [2.002420776, 3.369148926, 0.020630638], [2.371723633, 3.304030762, 0.020630638], [2.000697021, 3.359373291, 0.019420094], [2.37, 3.294255127, 0.019420094], [2.016210449, 3.4473540039999997, 0.030314986999999998], [2.3855134280000003, 3.382236084, 0.030314986999999998], [2.014486694, 3.4375783689999997, 0.029104445], [2.383789551, 3.372460449, 0.029104445], [2.030000122, 3.525559326, 0.039999335999999996], [2.3993029790000002, 3.460441162, 0.039999335999999996], [2.028276367, 3.515783691, 0.038788795], [2.397579346, 3.450665527, 0.038788795], [2.4129809570000003, 3.209763184, 0.010182931], [1.98, 3.875, 0.925], [1.803, 3.088, 0.95], [1.98, 3.875, 0.95], [1.803, 3.088, 0.925], [2.454, 2.973, 0.925], [2.454, 2.973, 0.95], [4.262, 2.973, 0.925], [4.262, 2.973, 0.95], [4.912, 3.088, 0.95], [4.912, 3.088, 0.925], [4.776, 3.875, 0.925], [4.776, 3.875, 0.95], [4.208, 3.774846191, 0.925], [4.208, 3.774846191, 0.95], [2.548, 3.774846191, 0.95], [2.548, 3.774846191, 0.925]], "color": [25, 25, 25], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Raised Floor": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [9, 8, 6], [7, 9, 6], [11, 10, 9], [9, 10, 8], [2, 0, 10], [11, 2, 10], [1, 10, 0], [1, 4, 10], [4, 8, 10], [4, 6, 8], [3, 2, 11], [3, 11, 5], [5, 11, 9], [5, 9, 7]], "pts": [[1.8688900149999998, 3.281033203, 0.0], [1.385890015, 0.65, 0.0], [1.8688900149999998, 3.281033203, 0.268], [1.385890015, 0.65, 0.268], [5.389890137, 0.65, 0.0], [5.389890137, 0.65, 0.268], [4.906890137, 3.281033203, 0.0], [4.906890137, 3.281033203, 0.268], [4.275890137, 3.157388672, 0.0], [4.275890137, 3.157388672, 0.268], [2.501889893, 3.157388672, 0.0], [2.501889893, 3.157388672, 0.268]], "color": [25, 25, 25], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Back": {"tris": [[3, 5, 6], [4, 3, 6], [4, 6, 7], [1, 4, 7], [2, 1, 7], [2, 7, 8], [0, 2, 8], [0, 8, 9]], "pts": [[-0.0, 1.476, 0.0], [3.387889893, 0.0, 0.0], [0.701876343, 0.473616669, 0.0], [6.775779785, 1.476, 0.0], [6.073903809, 0.473616669, 0.0], [6.775779785, 1.476, 4.435], [6.073903809, 0.473616669, 4.1090214839999994], [3.387889893, -0.0, 3.955], [0.701876343, 0.473616669, 4.1090214839999994], [-0.0, 1.476, 4.435]], "color": [100, 100, 100], "sides": [1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Front": {"tris": [[0, 1, 2], [0, 2, 3], [5, 0, 3], [5, 3, 6], [2, 7, 6], [2, 6, 3], [4, 7, 2], [1, 4, 2]], "pts": [[4.857242188, 7.601, 0.0], [1.9185378419999999, 7.601, 0.0], [1.9185378419999999, 7.601, 2.0], [4.857242188, 7.601, 2.0], [0.9849625240000001, 7.062, 0.0], [5.790817382999999, 7.062, 0.0], [5.790817382999999, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45]], "color": [100, 100, 100], "sides": [1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Side": {"tris": [[2, 3, 4], [2, 4, 6], [5, 0, 7], [1, 0, 5]], "pts": [[-0.0, 1.476, 0.0], [0.9849625240000001, 7.062, 0.0], [6.775779785, 1.476, 0.0], [5.790817382999999, 7.062, 0.0], [5.790817382999999, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45], [6.775779785, 1.476, 4.435], [-0.0, 1.476, 4.435]], "color": [180, 180, 180], "sides": [1, 1, 1, 1]}, "Windows": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 6, 0], [4, 6, 1], [3, 2, 7], [5, 3, 7], [10, 9, 8], [11, 10, 8], [11, 8, 12], [13, 11, 12], [13, 12, 14], [15, 13, 14], [15, 14, 9], [10, 15, 9], [8, 14, 12], [8, 9, 14], [11, 13, 15], [11, 15, 10]], "pts": [[5.933484862999999, 5.926368164, 0.0], [6.237890137, 4.2, 0.0], [5.933484862999999, 5.926368164, 2.0], [6.237890137, 4.2, 2.0], [6.336370605, 4.217364746, 0.0], [6.336370605, 4.217364746, 2.0], [6.0319653319999995, 5.94373291, 0.0], [6.0319653319999995, 5.94373291, 2.0], [0.537890015, 4.2, 0.0], [0.8422952269999999, 5.926368164, 0.0], [0.8422952269999999, 5.926368164, 2.0], [0.537890015, 4.2, 2.0], [0.43940921, 4.217364746, 0.0], [0.43940921, 4.217364746, 2.0], [0.7438144529999999, 5.94373291, 0.0], [0.7438144529999999, 5.94373291, 2.0]], "color": [137, 207, 240], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "sources": [{"name": "S1", "xyz": [1.7438900000000002, 6.736571428571429, 1.3940000000000001]}, {"name": "S2", "xyz": [5.03189, 6.736571428571429, 1.3940000000000001]}], "receivers": [{"name": "R1", "xyz": [3.38789, 4.889079900928195, 1.2]}, {"name": "R2", "xyz": [3.38789, 3.889079900928195, 1.2]}, {"name": "R3", "xyz": [3.38789, 4.089079900928195, 1.2]}, {"name": "R4", "xyz": [3.38789, 4.289079900928195, 1.2]}, {"name": "R5", "xyz": [3.38789, 4.4890799009281945, 1.2]}, {"name": "R6", "xyz": [3.38789, 4.689079900928195, 1.2]}]} +{"mats_hash": {"ATC Left": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 4, 0], [4, 6, 0], [3, 2, 5], [5, 2, 7]], "pts": [[1.305857422, 7.146186523, 1.062], [1.517857422, 6.7789916990000005, 1.062], [1.305857422, 7.146186523, 1.894], [1.517857422, 6.7789916990000005, 1.894], [2.148323975, 7.142991699, 1.062], [2.148323975, 7.142991699, 1.894], [1.9363238530000002, 7.510186523000001, 1.062], [1.9363238530000002, 7.510186523000001, 1.894]], "color": [5, 5, 5], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "ATC Right": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 6, 0], [4, 6, 1], [3, 2, 7], [5, 3, 7]], "pts": [[4.839456054999999, 7.510186523000001, 1.062], [4.627456055, 7.142991699, 1.062], [4.839456054999999, 7.510186523000001, 1.894], [4.627456055, 7.142991699, 1.894], [5.257922852, 6.7789916990000005, 1.062], [5.257922852, 6.7789916990000005, 1.894], [5.469922852, 7.146186523, 1.062], [5.469922852, 7.146186523, 1.894]], "color": [5, 5, 5], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Ceiling": {"tris": [[6, 2, 0], [1, 6, 0], [4, 3, 2], [6, 5, 4], [6, 4, 2]], "pts": [[5.790823242, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45], [6.775785645, 1.476, 4.435], [6.073909668000001, 0.473617249, 4.109021973], [3.387892822, -0.0, 3.955], [0.701875977, 0.473617249, 4.109021973], [-0.0, 1.476, 4.435]], "color": [60, 60, 60], "sides": [1, 1, 1, 1, 1]}, "Console": {"tris": [[1, 0, 13], [1, 13, 12], [2, 1, 12], [2, 12, 10], [2, 10, 14], [3, 2, 14], [4, 3, 14], [4, 14, 15], [5, 4, 15], [5, 15, 16], [6, 5, 16], [6, 16, 17], [7, 6, 17], [7, 17, 8], [9, 7, 8], [9, 8, 11], [0, 9, 11], [0, 11, 13], [12, 11, 10], [12, 13, 11], [11, 14, 10], [14, 8, 15], [11, 8, 14], [8, 16, 15], [8, 17, 16], [1, 2, 9], [1, 9, 0], [9, 2, 3], [3, 4, 7], [9, 3, 7], [7, 4, 5], [7, 5, 6]], "pts": [[5.203890137, 6.358, 1.025], [5.203890137, 6.518, 1.025], [5.203890137, 6.518, 0.476], [5.203890137, 5.753, 0.476], [5.203890137, 5.653, 0.647361084], [5.203890137, 5.283, 0.678], [5.203890137, 5.283, 0.736], [5.203890137, 5.653, 0.736], [1.571890015, 5.653, 0.736], [5.203890137, 6.263627441, 0.875], [1.571890015, 6.518, 0.476], [1.571890015, 6.263627441, 0.875], [1.571890015, 6.518, 1.025], [1.571890015, 6.358, 1.025], [1.571890015, 5.753, 0.476], [1.571890015, 5.653, 0.647361084], [1.571890015, 5.283, 0.678], [1.571890015, 5.283, 0.736]], "color": [60, 60, 60], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Couch": {"tris": [[1, 0, 12], [2, 0, 1], [2, 3, 0], [4, 3, 2], [4, 5, 3], [6, 5, 4], [6, 12, 5], [1, 12, 6], [0, 5, 12], [3, 5, 0], [2, 1, 6], [4, 2, 6], [9, 8, 7], [10, 8, 9], [10, 0, 8], [11, 0, 10], [11, 12, 0], [13, 12, 11], [13, 7, 12], [9, 7, 13], [8, 12, 7], [0, 12, 8], [10, 9, 13], [11, 10, 13], [14, 3, 5], [15, 3, 14], [15, 16, 3], [17, 16, 15], [17, 18, 16], [19, 18, 17], [19, 5, 18], [14, 5, 19], [3, 18, 5], [16, 18, 3], [15, 14, 19], [17, 15, 19], [21, 20, 1], [20, 22, 1], [20, 23, 22], [23, 24, 22], [23, 25, 24], [25, 26, 24], [25, 21, 26], [21, 1, 26], [22, 24, 1], [24, 26, 1], [20, 21, 23], [23, 21, 25], [25, 23, 26], [23, 24, 26], [23, 27, 24], [27, 28, 24], [27, 29, 28], [29, 30, 28], [29, 25, 30], [25, 26, 30], [24, 28, 26], [28, 30, 26], [23, 25, 27], [27, 25, 29], [29, 27, 30], [27, 28, 30], [27, 31, 28], [31, 32, 28], [31, 33, 32], [33, 34, 32], [33, 29, 34], [29, 30, 34], [28, 32, 30], [32, 34, 30], [27, 29, 31], [31, 29, 33], [33, 31, 34], [31, 32, 34], [31, 35, 32], [35, 36, 32], [35, 37, 36], [37, 6, 36], [37, 33, 6], [33, 34, 6], [32, 36, 34], [36, 6, 34], [31, 33, 35], [35, 33, 37]], "pts": [[1.927890015, 0.67, 0.328], [1.927890015, 1.65, 0.508], [1.927890015, 0.67, 0.508], [4.847890137, 0.67, 0.328], [4.847890137, 0.67, 0.508], [4.847890137, 1.65, 0.328], [4.847890137, 1.65, 0.508], [1.7778900149999999, 1.65, 0.328], [1.7778900149999999, 0.67, 0.328], [1.7778900149999999, 1.65, 1.008], [1.7778900149999999, 0.67, 1.008], [1.927890015, 0.67, 1.008], [1.927890015, 1.65, 0.328], [1.927890015, 1.65, 1.008], [4.847890137, 1.65, 1.008], [4.847890137, 0.67, 1.008], [4.997890137000001, 0.67, 0.328], [4.997890137000001, 0.67, 1.008], [4.997890137000001, 1.65, 0.328], [4.997890137000001, 1.65, 1.008], [1.927890015, 1.1, 0.748], [1.927890015, 1.65, 0.748], [1.927890015, 1.1, 0.508], [2.657889893, 1.1, 0.748], [2.657889893, 1.1, 0.508], [2.657889893, 1.65, 0.748], [2.657889893, 1.65, 0.508], [3.387889893, 1.1, 0.748], [3.387889893, 1.1, 0.508], [3.387889893, 1.65, 0.748], [3.387889893, 1.65, 0.508], [4.117890137000001, 1.1, 0.748], [4.117890137000001, 1.1, 0.508], [4.117890137000001, 1.65, 0.748], [4.117890137000001, 1.65, 0.508], [4.847890137, 1.1, 0.748], [4.847890137, 1.1, 0.508], [4.847890137, 1.65, 0.748]], "color": [5, 5, 48], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Floor": {"tris": [[2, 3, 4], [2, 5, 3], [3, 6, 7], [1, 8, 5], [0, 8, 1], [5, 8, 3], [3, 8, 6]], "pts": [[4.857248047, 7.601, 0.0], [1.9185378419999999, 7.601, 0.0], [-0.0, 1.476, 0.0], [3.387892822, -0.0, 0.0], [0.701875977, 0.473617249, 0.0], [0.9849625240000001, 7.062, 0.0], [6.775785645, 1.476, 0.0], [6.073909668000001, 0.473617249, 0.0], [5.790823242, 7.062, 0.0]], "color": [53, 33, 0], "sides": [1, 1, 1, 1, 1, 1, 1]}, "Outboard": {"tris": [[2, 1, 0], [3, 2, 0], [3, 0, 4], [5, 3, 4], [5, 4, 6], [7, 5, 6], [7, 6, 1], [2, 7, 1], [6, 0, 1], [6, 4, 0], [7, 2, 3], [7, 3, 5], [10, 9, 8], [11, 9, 10], [11, 12, 9], [13, 12, 11], [13, 14, 12], [15, 14, 13], [15, 10, 14], [10, 8, 14], [9, 12, 8], [12, 14, 8], [11, 10, 13], [13, 10, 15]], "pts": [[3.1381999510000003, 3.74, 0.719], [3.62, 3.74, 0.719], [3.62, 3.7375178219999996, 0.7187010500000001], [3.1381999510000003, 3.7375178219999996, 0.7187010500000001], [3.1381999510000003, 3.729597412, 0.805375854], [3.1381999510000003, 3.727115479, 0.805076904], [3.62, 3.729597412, 0.805375854], [3.62, 3.727115479, 0.805076904], [3.593600098, 3.509664307, 0.691259888], [3.593600098, 3.7375178219999996, 0.7187010500000001], [3.593600098, 3.499261963, 0.777635681], [3.593600098, 3.727115479, 0.805076904], [3.1646000979999998, 3.7375178219999996, 0.7187010500000001], [3.1646000979999998, 3.727115479, 0.805076904], [3.1646000979999998, 3.509664307, 0.691259888], [3.1646000979999998, 3.499261963, 0.777635681]], "color": [0, 0, 0], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Rack": {"tris": [[2, 1, 0], [0, 1, 3], [5, 4, 1], [2, 5, 1], [0, 3, 17], [54, 0, 17], [3, 7, 6], [3, 8, 7], [3, 9, 8], [3, 10, 9], [3, 11, 10], [3, 12, 11], [3, 13, 12], [3, 14, 13], [6, 15, 3], [8, 16, 7], [16, 17, 15], [15, 17, 3], [8, 18, 16], [16, 18, 17], [18, 19, 17], [21, 1, 20], [14, 1, 22], [24, 1, 23], [26, 1, 25], [3, 1, 14], [22, 1, 24], [23, 1, 21], [20, 1, 26], [9, 27, 19], [10, 27, 9], [19, 27, 17], [27, 28, 17], [1, 29, 25], [1, 30, 29], [11, 31, 28], [12, 31, 11], [28, 31, 17], [31, 32, 17], [13, 33, 32], [32, 33, 17], [14, 33, 13], [22, 35, 34], [24, 35, 22], [23, 37, 36], [21, 37, 23], [20, 39, 38], [26, 39, 20], [29, 40, 25], [29, 41, 40], [38, 4, 37], [41, 4, 40], [36, 4, 35], [40, 4, 39], [42, 4, 41], [30, 4, 42], [35, 4, 34], [1, 4, 30], [37, 4, 36], [39, 4, 38], [33, 4, 17], [34, 4, 33], [0, 44, 43], [0, 43, 45], [0, 45, 46], [0, 46, 47], [0, 47, 48], [0, 48, 49], [0, 49, 50], [0, 50, 51], [44, 0, 52], [45, 43, 53], [53, 52, 54], [52, 0, 54], [45, 53, 55], [53, 54, 55], [55, 54, 56], [51, 57, 2], [59, 58, 2], [61, 60, 2], [63, 62, 2], [0, 51, 2], [57, 59, 2], [58, 61, 2], [60, 63, 2], [46, 56, 64], [47, 46, 64], [56, 54, 64], [64, 54, 65], [2, 62, 66], [2, 66, 67], [48, 65, 68], [49, 48, 68], [65, 54, 68], [68, 54, 69], [69, 54, 70], [50, 69, 70], [51, 50, 70], [57, 72, 71], [59, 57, 71], [58, 74, 73], [61, 58, 73], [63, 60, 75], [63, 75, 76], [62, 78, 77], [66, 62, 77], [2, 67, 79], [73, 74, 5], [76, 75, 5], [77, 78, 5], [2, 79, 5], [74, 71, 5], [75, 73, 5], [78, 76, 5], [79, 77, 5], [71, 72, 5], [72, 54, 5], [70, 54, 72], [54, 17, 5], [5, 17, 4], [60, 61, 21], [60, 21, 20], [60, 20, 75], [75, 20, 38], [75, 38, 37], [73, 75, 37], [73, 37, 61], [61, 37, 21], [53, 7, 16], [43, 7, 53], [53, 15, 52], [53, 16, 15], [44, 15, 6], [52, 15, 44], [43, 6, 7], [44, 6, 43], [56, 9, 19], [46, 9, 56], [56, 18, 55], [56, 19, 18], [45, 18, 8], [55, 18, 45], [46, 8, 9], [45, 8, 46], [65, 11, 28], [48, 11, 65], [65, 27, 64], [65, 28, 27], [47, 27, 10], [64, 27, 47], [47, 11, 48], [47, 10, 11], [69, 50, 32], [50, 13, 32], [68, 32, 31], [69, 32, 68], [49, 31, 12], [68, 31, 49], [49, 13, 50], [49, 12, 13], [72, 57, 34], [57, 22, 34], [70, 72, 33], [72, 34, 33], [51, 70, 14], [70, 33, 14], [57, 14, 22], [51, 14, 57], [74, 58, 36], [58, 23, 36], [74, 35, 71], [74, 36, 35], [59, 71, 24], [71, 35, 24], [59, 23, 58], [59, 24, 23], [78, 62, 40], [62, 25, 40], [78, 39, 76], [78, 40, 39], [63, 76, 26], [76, 39, 26], [62, 26, 25], [63, 26, 62], [67, 66, 29], [67, 29, 30], [67, 30, 79], [79, 30, 42], [79, 42, 41], [77, 79, 41], [77, 41, 66], [66, 41, 29], [81, 80, 90], [80, 82, 90], [80, 161, 82], [83, 161, 80], [83, 84, 161], [84, 155, 161], [84, 90, 155], [81, 90, 84], [82, 155, 90], [161, 155, 82], [80, 81, 84], [83, 80, 84], [87, 85, 82], [86, 87, 82], [89, 88, 87], [87, 88, 85], [86, 82, 90], [91, 86, 90], [126, 93, 92], [125, 94, 126], [126, 94, 93], [125, 95, 94], [125, 96, 95], [88, 97, 85], [95, 97, 94], [125, 98, 96], [95, 99, 97], [97, 99, 85], [98, 100, 96], [99, 100, 85], [98, 101, 100], [98, 90, 102], [125, 90, 98], [102, 90, 103], [90, 104, 103], [103, 105, 102], [103, 106, 105], [104, 82, 107], [107, 82, 106], [90, 82, 104], [106, 82, 105], [100, 82, 85], [101, 82, 100], [105, 82, 101], [110, 109, 108], [217, 110, 111], [110, 108, 111], [217, 111, 112], [217, 112, 113], [89, 87, 114], [112, 111, 114], [217, 113, 115], [112, 114, 116], [114, 87, 116], [115, 113, 117], [116, 87, 117], [115, 117, 118], [115, 119, 91], [217, 115, 91], [119, 120, 91], [91, 120, 121], [120, 119, 122], [120, 122, 123], [124, 123, 86], [121, 124, 86], [91, 121, 86], [123, 122, 86], [118, 87, 86], [117, 87, 118], [122, 118, 86], [114, 97, 88], [89, 114, 88], [91, 90, 125], [217, 91, 125], [217, 125, 126], [110, 217, 126], [110, 126, 92], [109, 110, 92], [109, 92, 108], [108, 92, 93], [108, 93, 94], [111, 108, 94], [111, 94, 114], [114, 94, 97], [116, 117, 99], [117, 100, 99], [112, 99, 95], [116, 99, 112], [113, 95, 96], [112, 95, 113], [117, 113, 100], [113, 96, 100], [119, 102, 105], [122, 119, 105], [122, 105, 101], [118, 122, 101], [118, 101, 115], [115, 101, 98], [115, 98, 119], [119, 98, 102], [121, 104, 107], [124, 121, 107], [124, 107, 106], [123, 124, 106], [123, 106, 120], [120, 106, 103], [120, 103, 121], [121, 103, 104], [128, 127, 149], [161, 128, 149], [349, 129, 128], [128, 129, 127], [161, 149, 130], [155, 161, 130], [133, 132, 131], [135, 134, 133], [133, 134, 132], [135, 136, 134], [135, 137, 136], [129, 138, 127], [136, 138, 134], [135, 139, 137], [136, 140, 138], [138, 140, 127], [139, 141, 137], [140, 141, 127], [139, 142, 141], [139, 130, 143], [135, 130, 139], [143, 130, 144], [130, 145, 144], [144, 146, 143], [144, 147, 146], [145, 149, 148], [148, 149, 147], [130, 149, 145], [147, 149, 146], [141, 149, 127], [142, 149, 141], [146, 149, 142], [374, 150, 352], [373, 374, 151], [374, 352, 151], [373, 151, 353], [373, 353, 354], [349, 128, 355], [353, 151, 355], [373, 354, 152], [353, 355, 356], [355, 128, 356], [152, 354, 153], [356, 128, 153], [152, 153, 154], [152, 156, 155], [373, 152, 155], [156, 157, 155], [155, 157, 158], [157, 156, 159], [157, 159, 160], [162, 160, 161], [158, 162, 161], [155, 158, 161], [160, 159, 161], [154, 128, 161], [153, 128, 154], [159, 154, 161], [355, 138, 129], [349, 355, 129], [155, 130, 135], [373, 155, 135], [373, 135, 133], [374, 373, 133], [374, 133, 131], [150, 374, 131], [150, 131, 352], [352, 131, 132], [352, 132, 134], [151, 352, 134], [151, 134, 355], [355, 134, 138], [356, 153, 140], [153, 141, 140], [353, 140, 136], [356, 140, 353], [354, 136, 137], [353, 136, 354], [153, 354, 141], [354, 137, 141], [156, 143, 146], [159, 156, 146], [159, 146, 142], [154, 159, 142], [154, 142, 152], [152, 142, 139], [152, 139, 156], [156, 139, 143], [158, 145, 148], [162, 158, 148], [162, 148, 147], [160, 162, 147], [160, 147, 157], [157, 147, 144], [157, 144, 158], [158, 144, 145], [213, 164, 163], [213, 163, 165], [213, 165, 166], [213, 166, 167], [213, 167, 168], [213, 168, 169], [213, 169, 170], [213, 170, 171], [213, 171, 172], [213, 172, 173], [213, 173, 174], [213, 174, 175], [164, 213, 214], [164, 214, 176], [176, 214, 177], [165, 163, 177], [177, 214, 178], [165, 177, 178], [178, 214, 179], [167, 166, 179], [179, 214, 180], [167, 179, 180], [180, 214, 181], [169, 168, 181], [184, 183, 182], [186, 185, 182], [188, 187, 182], [175, 189, 182], [191, 190, 182], [192, 184, 182], [183, 186, 182], [185, 188, 182], [187, 191, 182], [190, 193, 182], [213, 175, 182], [189, 192, 182], [169, 181, 194], [181, 214, 194], [171, 170, 195], [171, 195, 196], [173, 172, 197], [173, 197, 198], [175, 174, 199], [175, 199, 200], [192, 189, 201], [192, 201, 202], [183, 184, 203], [184, 204, 203], [185, 186, 205], [185, 205, 206], [187, 188, 207], [187, 207, 208], [190, 191, 209], [190, 209, 210], [182, 193, 211], [207, 206, 216], [209, 208, 216], [182, 211, 216], [211, 210, 216], [208, 207, 216], [210, 209, 216], [194, 214, 216], [195, 194, 216], [196, 195, 216], [197, 196, 216], [198, 197, 216], [199, 198, 216], [200, 199, 216], [201, 200, 216], [202, 201, 216], [204, 202, 216], [203, 204, 216], [205, 203, 216], [206, 205, 216], [213, 212, 215], [213, 215, 214], [216, 215, 217], [216, 214, 215], [182, 135, 212], [182, 212, 213], [135, 182, 216], [135, 216, 217], [219, 218, 166], [218, 179, 166], [220, 179, 218], [220, 178, 179], [220, 221, 178], [221, 165, 178], [219, 165, 221], [219, 166, 165], [223, 222, 170], [222, 195, 170], [224, 195, 222], [224, 194, 195], [224, 225, 194], [225, 169, 194], [223, 169, 225], [223, 170, 169], [227, 226, 174], [226, 199, 174], [228, 199, 226], [228, 198, 199], [228, 229, 198], [229, 173, 198], [227, 173, 229], [227, 174, 173], [231, 230, 189], [230, 201, 189], [232, 201, 230], [232, 200, 201], [232, 233, 200], [233, 175, 200], [233, 189, 175], [231, 189, 233], [235, 234, 184], [234, 204, 184], [236, 204, 234], [236, 202, 204], [236, 237, 202], [237, 192, 202], [237, 184, 192], [235, 184, 237], [239, 238, 186], [238, 205, 186], [240, 205, 238], [240, 203, 205], [240, 241, 203], [241, 183, 203], [239, 183, 241], [239, 186, 183], [243, 242, 188], [242, 207, 188], [244, 207, 242], [244, 206, 207], [244, 245, 206], [245, 185, 206], [245, 188, 185], [243, 188, 245], [247, 246, 191], [246, 209, 191], [248, 209, 246], [248, 208, 209], [248, 249, 208], [249, 187, 208], [249, 191, 187], [247, 191, 249], [251, 250, 193], [250, 211, 193], [252, 211, 250], [252, 210, 211], [252, 190, 210], [253, 190, 252], [253, 193, 190], [251, 193, 253], [255, 254, 163], [254, 177, 163], [256, 177, 254], [256, 176, 177], [256, 257, 176], [257, 164, 176], [257, 163, 164], [255, 163, 257], [259, 258, 168], [258, 181, 168], [260, 181, 258], [260, 180, 181], [260, 261, 180], [261, 167, 180], [261, 168, 167], [259, 168, 261], [263, 262, 172], [262, 197, 172], [264, 197, 262], [264, 196, 197], [264, 265, 196], [265, 171, 196], [263, 171, 265], [263, 172, 171], [135, 251, 253], [135, 253, 247], [135, 247, 249], [135, 249, 243], [135, 243, 245], [135, 245, 239], [135, 239, 241], [135, 241, 235], [135, 235, 237], [135, 237, 231], [135, 231, 233], [135, 217, 250], [251, 135, 250], [250, 217, 252], [247, 253, 252], [252, 217, 246], [247, 252, 246], [246, 217, 248], [248, 217, 242], [249, 248, 242], [243, 249, 242], [242, 217, 244], [265, 223, 212], [225, 259, 212], [261, 219, 212], [221, 255, 212], [223, 225, 212], [219, 221, 212], [227, 229, 212], [263, 265, 212], [259, 261, 212], [255, 257, 212], [135, 233, 212], [233, 227, 212], [229, 263, 212], [245, 244, 238], [239, 245, 238], [241, 240, 234], [235, 241, 234], [231, 237, 236], [231, 236, 230], [233, 232, 226], [227, 233, 226], [229, 228, 262], [263, 229, 262], [223, 265, 222], [265, 264, 222], [259, 225, 258], [225, 224, 258], [219, 261, 218], [261, 260, 218], [255, 221, 254], [221, 220, 254], [212, 257, 256], [220, 218, 215], [260, 258, 215], [212, 256, 215], [258, 224, 215], [218, 260, 215], [256, 254, 215], [254, 220, 215], [264, 217, 215], [262, 217, 264], [228, 217, 262], [226, 217, 228], [232, 217, 226], [230, 217, 232], [236, 217, 230], [234, 217, 236], [240, 217, 234], [238, 217, 240], [244, 217, 238], [222, 264, 215], [224, 222, 215], [268, 267, 266], [266, 267, 269], [345, 270, 267], [268, 345, 267], [266, 269, 271], [319, 266, 271], [269, 273, 272], [269, 274, 273], [269, 275, 274], [269, 276, 275], [269, 277, 276], [269, 278, 277], [269, 279, 278], [269, 280, 279], [272, 281, 269], [274, 282, 273], [282, 271, 281], [281, 271, 269], [274, 283, 282], [282, 283, 271], [283, 284, 271], [286, 267, 285], [280, 267, 287], [289, 267, 288], [291, 267, 290], [269, 267, 280], [287, 267, 289], [288, 267, 286], [285, 267, 291], [275, 292, 284], [276, 292, 275], [284, 292, 271], [292, 293, 271], [267, 294, 290], [267, 295, 294], [277, 296, 293], [278, 296, 277], [293, 296, 271], [296, 297, 271], [279, 298, 297], [297, 298, 271], [280, 298, 279], [287, 300, 299], [289, 300, 287], [288, 302, 301], [286, 302, 288], [285, 304, 303], [291, 304, 285], [294, 305, 290], [294, 306, 305], [303, 270, 302], [306, 270, 305], [301, 270, 300], [305, 270, 304], [307, 270, 306], [295, 270, 307], [300, 270, 299], [267, 270, 295], [302, 270, 301], [304, 270, 303], [298, 270, 271], [299, 270, 298], [266, 309, 308], [266, 308, 310], [266, 310, 311], [266, 311, 312], [266, 312, 313], [266, 313, 314], [266, 314, 315], [266, 315, 316], [309, 266, 317], [310, 308, 318], [318, 317, 319], [317, 266, 319], [310, 318, 320], [318, 319, 320], [320, 319, 321], [316, 322, 268], [324, 323, 268], [326, 325, 268], [328, 327, 268], [266, 316, 268], [322, 324, 268], [323, 326, 268], [325, 328, 268], [311, 321, 329], [312, 311, 329], [321, 319, 329], [329, 319, 330], [268, 327, 331], [268, 331, 332], [313, 330, 333], [314, 313, 333], [330, 319, 333], [333, 319, 334], [334, 319, 335], [315, 334, 335], [316, 315, 335], [322, 337, 336], [324, 322, 336], [323, 339, 338], [326, 323, 338], [328, 325, 340], [328, 340, 341], [327, 343, 342], [331, 327, 342], [268, 332, 344], [338, 339, 345], [341, 340, 345], [342, 343, 345], [268, 344, 345], [339, 336, 345], [340, 338, 345], [343, 341, 345], [344, 342, 345], [336, 337, 345], [337, 319, 345], [335, 319, 337], [319, 271, 345], [345, 271, 270], [325, 326, 286], [325, 286, 285], [325, 285, 340], [340, 285, 303], [340, 303, 302], [338, 340, 302], [338, 302, 326], [326, 302, 286], [318, 273, 282], [308, 273, 318], [318, 281, 317], [318, 282, 281], [309, 281, 272], [317, 281, 309], [308, 272, 273], [309, 272, 308], [321, 275, 284], [311, 275, 321], [321, 283, 320], [321, 284, 283], [310, 283, 274], [320, 283, 310], [311, 274, 275], [310, 274, 311], [330, 277, 293], [313, 277, 330], [330, 292, 329], [330, 293, 292], [312, 292, 276], [329, 292, 312], [312, 277, 313], [312, 276, 277], [334, 315, 297], [315, 279, 297], [333, 297, 296], [334, 297, 333], [314, 296, 278], [333, 296, 314], [314, 279, 315], [314, 278, 279], [337, 322, 299], [322, 287, 299], [335, 337, 298], [337, 299, 298], [316, 335, 280], [335, 298, 280], [322, 280, 287], [316, 280, 322], [339, 323, 301], [323, 288, 301], [339, 300, 336], [339, 301, 300], [324, 336, 289], [336, 300, 289], [324, 288, 323], [324, 289, 288], [343, 327, 305], [327, 290, 305], [343, 304, 341], [343, 305, 304], [328, 341, 291], [341, 304, 291], [327, 291, 290], [328, 291, 327], [332, 331, 294], [332, 294, 295], [332, 295, 344], [344, 295, 307], [344, 307, 306], [342, 344, 306], [342, 306, 331], [331, 306, 294], [84, 83, 155], [83, 161, 155], [83, 409, 161], [346, 409, 83], [346, 347, 409], [347, 381, 409], [347, 155, 381], [84, 155, 347], [161, 381, 155], [409, 381, 161], [83, 84, 347], [346, 83, 347], [348, 128, 161], [371, 348, 161], [350, 349, 348], [348, 349, 128], [371, 161, 155], [351, 371, 155], [374, 352, 150], [373, 151, 374], [374, 151, 352], [373, 353, 151], [373, 354, 353], [349, 355, 128], [353, 355, 151], [373, 152, 354], [353, 356, 355], [355, 356, 128], [152, 153, 354], [356, 153, 128], [152, 154, 153], [152, 155, 156], [373, 155, 152], [156, 155, 157], [155, 158, 157], [157, 159, 156], [157, 160, 159], [158, 161, 162], [162, 161, 160], [155, 161, 158], [160, 161, 159], [153, 161, 128], [154, 161, 153], [159, 161, 154], [375, 376, 357], [464, 375, 358], [375, 357, 358], [464, 358, 359], [464, 359, 360], [350, 348, 361], [359, 358, 361], [464, 360, 362], [359, 361, 363], [361, 348, 363], [362, 360, 364], [363, 348, 364], [362, 364, 365], [362, 366, 351], [464, 362, 351], [366, 367, 351], [351, 367, 368], [367, 366, 369], [367, 369, 370], [372, 370, 371], [368, 372, 371], [351, 368, 371], [370, 369, 371], [365, 348, 371], [364, 348, 365], [369, 365, 371], [361, 355, 349], [350, 361, 349], [351, 155, 373], [464, 351, 373], [464, 373, 374], [375, 464, 374], [375, 374, 150], [376, 375, 150], [376, 150, 357], [357, 150, 352], [357, 352, 151], [358, 357, 151], [358, 151, 361], [361, 151, 355], [363, 364, 356], [364, 153, 356], [359, 356, 353], [363, 356, 359], [360, 353, 354], [359, 353, 360], [364, 360, 153], [360, 354, 153], [366, 156, 159], [369, 366, 159], [369, 159, 154], [365, 369, 154], [365, 154, 362], [362, 154, 152], [362, 152, 366], [366, 152, 156], [368, 158, 162], [372, 368, 162], [372, 162, 160], [370, 372, 160], [370, 160, 367], [367, 160, 157], [367, 157, 368], [368, 157, 158], [379, 378, 377], [409, 379, 377], [600, 380, 379], [379, 380, 378], [409, 377, 392], [381, 409, 392], [411, 382, 412], [515, 383, 411], [411, 383, 382], [515, 384, 383], [515, 385, 384], [380, 386, 378], [384, 386, 383], [515, 387, 385], [384, 388, 386], [386, 388, 378], [387, 389, 385], [388, 389, 378], [387, 390, 389], [387, 392, 391], [515, 392, 387], [391, 392, 393], [392, 394, 393], [393, 395, 391], [393, 396, 395], [394, 377, 397], [397, 377, 396], [392, 377, 394], [396, 377, 395], [389, 377, 378], [390, 377, 389], [395, 377, 390], [399, 398, 602], [625, 399, 400], [399, 602, 400], [625, 400, 401], [625, 401, 402], [600, 379, 603], [401, 400, 603], [625, 402, 604], [401, 603, 403], [603, 379, 403], [604, 402, 605], [403, 379, 605], [604, 605, 606], [604, 404, 381], [625, 604, 381], [404, 405, 381], [381, 405, 406], [405, 404, 407], [405, 407, 408], [410, 408, 409], [406, 410, 409], [381, 406, 409], [408, 407, 409], [606, 379, 409], [605, 379, 606], [407, 606, 409], [603, 386, 380], [600, 603, 380], [381, 392, 515], [625, 381, 515], [625, 515, 411], [399, 625, 411], [399, 411, 412], [398, 399, 412], [398, 412, 602], [602, 412, 382], [602, 382, 383], [400, 602, 383], [400, 383, 603], [603, 383, 386], [403, 605, 388], [605, 389, 388], [401, 388, 384], [403, 388, 401], [402, 384, 385], [401, 384, 402], [605, 402, 389], [402, 385, 389], [404, 391, 395], [407, 404, 395], [407, 395, 390], [606, 407, 390], [606, 390, 604], [604, 390, 387], [604, 387, 404], [404, 387, 391], [406, 394, 397], [410, 406, 397], [410, 397, 396], [408, 410, 396], [408, 396, 405], [405, 396, 393], [405, 393, 406], [406, 393, 394], [466, 414, 413], [466, 413, 415], [466, 415, 416], [466, 416, 417], [466, 417, 418], [466, 418, 419], [466, 419, 420], [466, 420, 421], [466, 421, 422], [466, 422, 423], [466, 423, 424], [466, 424, 425], [414, 466, 426], [414, 426, 427], [427, 426, 428], [415, 413, 428], [428, 426, 429], [415, 428, 429], [429, 426, 430], [417, 416, 430], [430, 426, 431], [417, 430, 431], [431, 426, 432], [419, 418, 432], [435, 434, 433], [437, 436, 433], [439, 438, 433], [425, 440, 433], [442, 441, 433], [443, 435, 433], [434, 437, 433], [436, 439, 433], [438, 442, 433], [441, 444, 433], [466, 425, 433], [440, 443, 433], [419, 432, 445], [432, 426, 445], [421, 420, 446], [421, 446, 447], [423, 422, 448], [423, 448, 449], [425, 424, 450], [425, 450, 451], [443, 440, 452], [443, 452, 453], [434, 435, 454], [435, 455, 454], [436, 437, 456], [436, 456, 457], [438, 439, 458], [438, 458, 459], [441, 442, 460], [441, 460, 461], [433, 444, 462], [458, 457, 465], [460, 459, 465], [433, 462, 465], [462, 461, 465], [459, 458, 465], [461, 460, 465], [445, 426, 465], [446, 445, 465], [447, 446, 465], [448, 447, 465], [449, 448, 465], [450, 449, 465], [451, 450, 465], [452, 451, 465], [453, 452, 465], [455, 453, 465], [454, 455, 465], [456, 454, 465], [457, 456, 465], [466, 516, 463], [466, 463, 426], [465, 463, 464], [465, 426, 463], [433, 515, 516], [433, 516, 466], [515, 433, 465], [515, 465, 464], [468, 467, 416], [467, 430, 416], [469, 430, 467], [469, 429, 430], [469, 470, 429], [470, 415, 429], [468, 415, 470], [468, 416, 415], [472, 471, 420], [471, 446, 420], [473, 446, 471], [473, 445, 446], [473, 474, 445], [474, 419, 445], [472, 419, 474], [472, 420, 419], [476, 475, 424], [475, 450, 424], [477, 450, 475], [477, 449, 450], [477, 478, 449], [478, 423, 449], [476, 423, 478], [476, 424, 423], [480, 479, 440], [479, 452, 440], [481, 452, 479], [481, 451, 452], [481, 482, 451], [482, 425, 451], [482, 440, 425], [480, 440, 482], [484, 483, 435], [483, 455, 435], [485, 455, 483], [485, 453, 455], [485, 486, 453], [486, 443, 453], [486, 435, 443], [484, 435, 486], [488, 487, 437], [487, 456, 437], [489, 456, 487], [489, 454, 456], [489, 490, 454], [490, 434, 454], [488, 434, 490], [488, 437, 434], [492, 491, 439], [491, 458, 439], [493, 458, 491], [493, 457, 458], [493, 494, 457], [494, 436, 457], [494, 439, 436], [492, 439, 494], [496, 495, 442], [495, 460, 442], [497, 460, 495], [497, 459, 460], [497, 498, 459], [498, 438, 459], [498, 442, 438], [496, 442, 498], [500, 499, 444], [499, 462, 444], [501, 462, 499], [501, 461, 462], [501, 441, 461], [502, 441, 501], [502, 444, 441], [500, 444, 502], [504, 503, 413], [503, 428, 413], [505, 428, 503], [505, 427, 428], [505, 506, 427], [506, 414, 427], [506, 413, 414], [504, 413, 506], [508, 507, 418], [507, 432, 418], [509, 432, 507], [509, 431, 432], [509, 510, 431], [510, 417, 431], [510, 418, 417], [508, 418, 510], [512, 511, 422], [511, 448, 422], [513, 448, 511], [513, 447, 448], [513, 514, 447], [514, 421, 447], [512, 421, 514], [512, 422, 421], [515, 500, 502], [515, 502, 496], [515, 496, 498], [515, 498, 492], [515, 492, 494], [515, 494, 488], [515, 488, 490], [515, 490, 484], [515, 484, 486], [515, 486, 480], [515, 480, 482], [515, 464, 499], [500, 515, 499], [499, 464, 501], [496, 502, 501], [501, 464, 495], [496, 501, 495], [495, 464, 497], [497, 464, 491], [498, 497, 491], [492, 498, 491], [491, 464, 493], [514, 472, 516], [474, 508, 516], [510, 468, 516], [470, 504, 516], [472, 474, 516], [468, 470, 516], [476, 478, 516], [512, 514, 516], [508, 510, 516], [504, 506, 516], [515, 482, 516], [482, 476, 516], [478, 512, 516], [494, 493, 487], [488, 494, 487], [490, 489, 483], [484, 490, 483], [480, 486, 485], [480, 485, 479], [482, 481, 475], [476, 482, 475], [478, 477, 511], [512, 478, 511], [472, 514, 471], [514, 513, 471], [508, 474, 507], [474, 473, 507], [468, 510, 467], [510, 509, 467], [504, 470, 503], [470, 469, 503], [516, 506, 505], [469, 467, 463], [509, 507, 463], [516, 505, 463], [507, 473, 463], [467, 509, 463], [505, 503, 463], [503, 469, 463], [513, 464, 463], [511, 464, 513], [477, 464, 511], [475, 464, 477], [481, 464, 475], [479, 464, 481], [485, 464, 479], [483, 464, 485], [489, 464, 483], [487, 464, 489], [493, 464, 487], [471, 513, 463], [473, 471, 463], [519, 518, 517], [517, 518, 520], [522, 521, 518], [519, 522, 518], [517, 520, 523], [524, 517, 523], [520, 526, 525], [520, 527, 526], [520, 528, 527], [520, 529, 528], [520, 530, 529], [520, 531, 530], [520, 532, 531], [520, 533, 532], [525, 534, 520], [527, 535, 526], [535, 523, 534], [534, 523, 520], [527, 536, 535], [535, 536, 523], [536, 537, 523], [539, 518, 538], [533, 518, 540], [542, 518, 541], [544, 518, 543], [520, 518, 533], [540, 518, 542], [541, 518, 539], [538, 518, 544], [528, 545, 537], [529, 545, 528], [537, 545, 523], [545, 546, 523], [518, 547, 543], [518, 548, 547], [530, 549, 546], [531, 549, 530], [546, 549, 523], [549, 550, 523], [532, 551, 550], [550, 551, 523], [533, 551, 532], [540, 553, 552], [542, 553, 540], [541, 555, 554], [539, 555, 541], [538, 557, 556], [544, 557, 538], [547, 558, 543], [547, 559, 558], [556, 521, 555], [559, 521, 558], [554, 521, 553], [558, 521, 557], [560, 521, 559], [548, 521, 560], [553, 521, 552], [518, 521, 548], [555, 521, 554], [557, 521, 556], [551, 521, 523], [552, 521, 551], [517, 562, 561], [517, 561, 563], [517, 563, 564], [517, 564, 565], [517, 565, 566], [517, 566, 567], [517, 567, 568], [517, 568, 569], [562, 517, 570], [563, 561, 571], [571, 570, 524], [570, 517, 524], [563, 571, 572], [571, 524, 572], [572, 524, 573], [569, 574, 519], [576, 575, 519], [578, 577, 519], [580, 579, 519], [517, 569, 519], [574, 576, 519], [575, 578, 519], [577, 580, 519], [564, 573, 581], [565, 564, 581], [573, 524, 581], [581, 524, 582], [519, 579, 583], [519, 583, 584], [566, 582, 585], [567, 566, 585], [582, 524, 585], [585, 524, 586], [586, 524, 587], [568, 586, 587], [569, 568, 587], [574, 589, 588], [576, 574, 588], [575, 591, 590], [578, 575, 590], [580, 577, 592], [580, 592, 593], [579, 595, 594], [583, 579, 594], [519, 584, 596], [590, 591, 522], [593, 592, 522], [594, 595, 522], [519, 596, 522], [591, 588, 522], [592, 590, 522], [595, 593, 522], [596, 594, 522], [588, 589, 522], [589, 524, 522], [587, 524, 589], [524, 523, 522], [522, 523, 521], [577, 578, 539], [577, 539, 538], [577, 538, 592], [592, 538, 556], [592, 556, 555], [590, 592, 555], [590, 555, 578], [578, 555, 539], [571, 526, 535], [561, 526, 571], [571, 534, 570], [571, 535, 534], [562, 534, 525], [570, 534, 562], [561, 525, 526], [562, 525, 561], [573, 528, 537], [564, 528, 573], [573, 536, 572], [573, 537, 536], [563, 536, 527], [572, 536, 563], [564, 527, 528], [563, 527, 564], [582, 530, 546], [566, 530, 582], [582, 545, 581], [582, 546, 545], [565, 545, 529], [581, 545, 565], [565, 530, 566], [565, 529, 530], [586, 568, 550], [568, 532, 550], [585, 550, 549], [586, 550, 585], [567, 549, 531], [585, 549, 567], [567, 532, 568], [567, 531, 532], [589, 574, 552], [574, 540, 552], [587, 589, 551], [589, 552, 551], [569, 587, 533], [587, 551, 533], [574, 533, 540], [569, 533, 574], [591, 575, 554], [575, 541, 554], [591, 553, 588], [591, 554, 553], [576, 588, 542], [588, 553, 542], [576, 541, 575], [576, 542, 541], [595, 579, 558], [579, 543, 558], [595, 557, 593], [595, 558, 557], [580, 593, 544], [593, 557, 544], [579, 544, 543], [580, 544, 579], [584, 583, 547], [584, 547, 548], [584, 548, 596], [596, 548, 560], [596, 560, 559], [594, 596, 559], [594, 559, 583], [583, 559, 547], [347, 346, 381], [346, 409, 381], [346, 629, 409], [597, 629, 346], [597, 598, 629], [598, 633, 629], [598, 381, 633], [347, 381, 598], [409, 633, 381], [629, 633, 409], [346, 347, 598], [597, 346, 598], [599, 379, 409], [623, 599, 409], [601, 600, 599], [599, 600, 379], [623, 409, 381], [617, 623, 381], [399, 602, 398], [625, 400, 399], [399, 400, 602], [625, 401, 400], [625, 402, 401], [600, 603, 379], [401, 603, 400], [625, 604, 402], [401, 403, 603], [603, 403, 379], [604, 605, 402], [403, 605, 379], [604, 606, 605], [604, 381, 404], [625, 381, 604], [404, 381, 405], [381, 406, 405], [405, 407, 404], [405, 408, 407], [406, 409, 410], [410, 409, 408], [381, 409, 406], [408, 409, 407], [605, 409, 379], [606, 409, 605], [407, 409, 606], [627, 608, 607], [626, 627, 609], [627, 607, 609], [626, 609, 610], [626, 610, 611], [601, 599, 612], [610, 609, 612], [626, 611, 613], [610, 612, 614], [612, 599, 614], [613, 611, 615], [614, 599, 615], [613, 615, 616], [613, 618, 617], [626, 613, 617], [618, 619, 617], [617, 619, 620], [619, 618, 621], [619, 621, 622], [624, 622, 623], [620, 624, 623], [617, 620, 623], [622, 621, 623], [616, 599, 623], [615, 599, 616], [621, 616, 623], [612, 603, 600], [601, 612, 600], [617, 381, 625], [626, 617, 625], [626, 625, 399], [627, 626, 399], [627, 399, 398], [608, 627, 398], [608, 398, 607], [607, 398, 602], [607, 602, 400], [609, 607, 400], [609, 400, 612], [612, 400, 603], [614, 615, 403], [615, 605, 403], [610, 403, 401], [614, 403, 610], [611, 401, 402], [610, 401, 611], [615, 611, 605], [611, 402, 605], [618, 404, 407], [621, 618, 407], [621, 407, 606], [616, 621, 606], [616, 606, 613], [613, 606, 604], [613, 604, 618], [618, 604, 404], [620, 406, 410], [624, 620, 410], [624, 410, 408], [622, 624, 408], [622, 408, 619], [619, 408, 405], [619, 405, 620], [620, 405, 406], [630, 628, 652], [629, 630, 652], [632, 631, 630], [630, 631, 628], [629, 652, 646], [633, 629, 646], [636, 635, 634], [725, 637, 636], [636, 637, 635], [725, 638, 637], [725, 639, 638], [631, 640, 628], [638, 640, 637], [725, 641, 639], [638, 642, 640], [640, 642, 628], [641, 643, 639], [642, 643, 628], [641, 644, 643], [641, 646, 645], [725, 646, 641], [645, 646, 647], [646, 648, 647], [647, 649, 645], [647, 650, 649], [648, 652, 651], [651, 652, 650], [646, 652, 648], [650, 652, 649], [643, 652, 628], [644, 652, 643], [649, 652, 644], [655, 654, 653], [657, 655, 656], [655, 653, 656], [657, 656, 658], [657, 658, 659], [632, 630, 660], [658, 656, 660], [657, 659, 661], [658, 660, 662], [660, 630, 662], [661, 659, 663], [662, 630, 663], [661, 663, 664], [661, 665, 633], [657, 661, 633], [665, 666, 633], [633, 666, 667], [666, 665, 668], [666, 668, 669], [670, 669, 629], [667, 670, 629], [633, 667, 629], [669, 668, 629], [664, 630, 629], [663, 630, 664], [668, 664, 629], [660, 640, 631], [632, 660, 631], [633, 646, 725], [657, 633, 725], [657, 725, 636], [655, 657, 636], [655, 636, 634], [654, 655, 634], [654, 634, 653], [653, 634, 635], [653, 635, 637], [656, 653, 637], [656, 637, 660], [660, 637, 640], [662, 663, 642], [663, 643, 642], [658, 642, 638], [662, 642, 658], [659, 638, 639], [658, 638, 659], [663, 659, 643], [659, 639, 643], [665, 645, 649], [668, 665, 649], [668, 649, 644], [664, 668, 644], [664, 644, 661], [661, 644, 641], [661, 641, 665], [665, 641, 645], [667, 648, 651], [670, 667, 651], [670, 651, 650], [669, 670, 650], [669, 650, 666], [666, 650, 647], [666, 647, 667], [667, 647, 648], [723, 672, 671], [723, 671, 673], [723, 673, 674], [723, 674, 675], [723, 675, 676], [723, 676, 677], [723, 677, 678], [723, 678, 679], [723, 679, 680], [723, 680, 681], [723, 681, 682], [723, 682, 683], [672, 723, 684], [672, 684, 685], [685, 684, 686], [673, 671, 686], [686, 684, 687], [673, 686, 687], [687, 684, 688], [675, 674, 688], [688, 684, 689], [675, 688, 689], [689, 684, 690], [677, 676, 690], [692, 691, 724], [694, 693, 724], [696, 695, 724], [683, 697, 724], [699, 698, 724], [700, 692, 724], [691, 694, 724], [693, 696, 724], [695, 699, 724], [698, 701, 724], [723, 683, 724], [697, 700, 724], [677, 690, 702], [690, 684, 702], [679, 678, 703], [679, 703, 704], [681, 680, 705], [681, 705, 706], [683, 682, 707], [683, 707, 708], [700, 697, 709], [700, 709, 710], [691, 692, 711], [692, 712, 711], [693, 694, 713], [693, 713, 714], [695, 696, 715], [695, 715, 716], [698, 699, 717], [698, 717, 718], [724, 701, 719], [715, 714, 720], [717, 716, 720], [724, 719, 720], [719, 718, 720], [716, 715, 720], [718, 717, 720], [702, 684, 720], [703, 702, 720], [704, 703, 720], [705, 704, 720], [706, 705, 720], [707, 706, 720], [708, 707, 720], [709, 708, 720], [710, 709, 720], [712, 710, 720], [711, 712, 720], [713, 711, 720], [714, 713, 720], [723, 722, 721], [723, 721, 684], [720, 721, 626], [720, 684, 721], [724, 725, 722], [724, 722, 723], [725, 724, 720], [725, 720, 626], [727, 726, 674], [726, 688, 674], [728, 688, 726], [728, 687, 688], [728, 729, 687], [729, 673, 687], [727, 673, 729], [727, 674, 673], [731, 730, 678], [730, 703, 678], [732, 703, 730], [732, 702, 703], [732, 733, 702], [733, 677, 702], [731, 677, 733], [731, 678, 677], [735, 734, 682], [734, 707, 682], [736, 707, 734], [736, 706, 707], [736, 737, 706], [737, 681, 706], [735, 681, 737], [735, 682, 681], [739, 738, 697], [738, 709, 697], [740, 709, 738], [740, 708, 709], [740, 741, 708], [741, 683, 708], [741, 697, 683], [739, 697, 741], [743, 742, 692], [742, 712, 692], [744, 712, 742], [744, 710, 712], [744, 745, 710], [745, 700, 710], [745, 692, 700], [743, 692, 745], [747, 746, 694], [746, 713, 694], [748, 713, 746], [748, 711, 713], [748, 749, 711], [749, 691, 711], [747, 691, 749], [747, 694, 691], [751, 750, 696], [750, 715, 696], [752, 715, 750], [752, 714, 715], [752, 753, 714], [753, 693, 714], [753, 696, 693], [751, 696, 753], [755, 754, 699], [754, 717, 699], [756, 717, 754], [756, 716, 717], [756, 757, 716], [757, 695, 716], [757, 699, 695], [755, 699, 757], [759, 758, 701], [758, 719, 701], [760, 719, 758], [760, 718, 719], [760, 698, 718], [761, 698, 760], [761, 701, 698], [759, 701, 761], [763, 762, 671], [762, 686, 671], [764, 686, 762], [764, 685, 686], [764, 765, 685], [765, 672, 685], [765, 671, 672], [763, 671, 765], [767, 766, 676], [766, 690, 676], [768, 690, 766], [768, 689, 690], [768, 769, 689], [769, 675, 689], [769, 676, 675], [767, 676, 769], [771, 770, 680], [770, 705, 680], [772, 705, 770], [772, 704, 705], [772, 773, 704], [773, 679, 704], [771, 679, 773], [771, 680, 679], [725, 759, 761], [725, 761, 755], [725, 755, 757], [725, 757, 751], [725, 751, 753], [725, 753, 747], [725, 747, 749], [725, 749, 743], [725, 743, 745], [725, 745, 739], [725, 739, 741], [725, 626, 758], [759, 725, 758], [758, 626, 760], [755, 761, 760], [760, 626, 754], [755, 760, 754], [754, 626, 756], [756, 626, 750], [757, 756, 750], [751, 757, 750], [750, 626, 752], [773, 731, 722], [733, 767, 722], [769, 727, 722], [729, 763, 722], [731, 733, 722], [727, 729, 722], [735, 737, 722], [771, 773, 722], [767, 769, 722], [763, 765, 722], [725, 741, 722], [741, 735, 722], [737, 771, 722], [753, 752, 746], [747, 753, 746], [749, 748, 742], [743, 749, 742], [739, 745, 744], [739, 744, 738], [741, 740, 734], [735, 741, 734], [737, 736, 770], [771, 737, 770], [731, 773, 730], [773, 772, 730], [767, 733, 766], [733, 732, 766], [727, 769, 726], [769, 768, 726], [763, 729, 762], [729, 728, 762], [722, 765, 764], [728, 726, 721], [768, 766, 721], [722, 764, 721], [766, 732, 721], [726, 768, 721], [764, 762, 721], [762, 728, 721], [772, 626, 721], [770, 626, 772], [736, 626, 770], [734, 626, 736], [740, 626, 734], [738, 626, 740], [744, 626, 738], [742, 626, 744], [748, 626, 742], [746, 626, 748], [752, 626, 746], [730, 772, 721], [732, 730, 721], [776, 775, 774], [774, 775, 777], [779, 778, 775], [776, 779, 775], [774, 777, 780], [781, 774, 780], [777, 783, 782], [777, 784, 783], [777, 785, 784], [777, 786, 785], [777, 787, 786], [777, 788, 787], [777, 789, 788], [777, 790, 789], [782, 791, 777], [784, 792, 783], [792, 780, 791], [791, 780, 777], [784, 793, 792], [792, 793, 780], [793, 794, 780], [796, 775, 795], [790, 775, 797], [799, 775, 798], [801, 775, 800], [777, 775, 790], [797, 775, 799], [798, 775, 796], [795, 775, 801], [785, 802, 794], [786, 802, 785], [794, 802, 780], [802, 803, 780], [775, 804, 800], [775, 805, 804], [787, 806, 803], [788, 806, 787], [803, 806, 780], [806, 807, 780], [789, 808, 807], [807, 808, 780], [790, 808, 789], [797, 810, 809], [799, 810, 797], [798, 812, 811], [796, 812, 798], [795, 814, 813], [801, 814, 795], [804, 815, 800], [804, 816, 815], [813, 778, 812], [816, 778, 815], [811, 778, 810], [815, 778, 814], [817, 778, 816], [805, 778, 817], [810, 778, 809], [775, 778, 805], [812, 778, 811], [814, 778, 813], [808, 778, 780], [809, 778, 808], [774, 819, 818], [774, 818, 820], [774, 820, 821], [774, 821, 822], [774, 822, 823], [774, 823, 824], [774, 824, 825], [774, 825, 826], [819, 774, 827], [820, 818, 828], [828, 827, 781], [827, 774, 781], [820, 828, 829], [828, 781, 829], [829, 781, 830], [826, 831, 776], [833, 832, 776], [835, 834, 776], [837, 836, 776], [774, 826, 776], [831, 833, 776], [832, 835, 776], [834, 837, 776], [821, 830, 838], [822, 821, 838], [830, 781, 838], [838, 781, 839], [776, 836, 840], [776, 840, 841], [823, 839, 842], [824, 823, 842], [839, 781, 842], [842, 781, 843], [843, 781, 844], [825, 843, 844], [826, 825, 844], [831, 846, 845], [833, 831, 845], [832, 848, 847], [835, 832, 847], [837, 834, 849], [837, 849, 850], [836, 852, 851], [840, 836, 851], [776, 841, 853], [847, 848, 779], [850, 849, 779], [851, 852, 779], [776, 853, 779], [848, 845, 779], [849, 847, 779], [852, 850, 779], [853, 851, 779], [845, 846, 779], [846, 781, 779], [844, 781, 846], [781, 780, 779], [779, 780, 778], [834, 835, 796], [834, 796, 795], [834, 795, 849], [849, 795, 813], [849, 813, 812], [847, 849, 812], [847, 812, 835], [835, 812, 796], [828, 783, 792], [818, 783, 828], [828, 791, 827], [828, 792, 791], [819, 791, 782], [827, 791, 819], [818, 782, 783], [819, 782, 818], [830, 785, 794], [821, 785, 830], [830, 793, 829], [830, 794, 793], [820, 793, 784], [829, 793, 820], [821, 784, 785], [820, 784, 821], [839, 787, 803], [823, 787, 839], [839, 802, 838], [839, 803, 802], [822, 802, 786], [838, 802, 822], [822, 787, 823], [822, 786, 787], [843, 825, 807], [825, 789, 807], [842, 807, 806], [843, 807, 842], [824, 806, 788], [842, 806, 824], [824, 789, 825], [824, 788, 789], [846, 831, 809], [831, 797, 809], [844, 846, 808], [846, 809, 808], [826, 844, 790], [844, 808, 790], [831, 790, 797], [826, 790, 831], [848, 832, 811], [832, 798, 811], [848, 810, 845], [848, 811, 810], [833, 845, 799], [845, 810, 799], [833, 798, 832], [833, 799, 798], [852, 836, 815], [836, 800, 815], [852, 814, 850], [852, 815, 814], [837, 850, 801], [850, 814, 801], [836, 801, 800], [837, 801, 836], [841, 840, 804], [841, 804, 805], [841, 805, 853], [853, 805, 817], [853, 817, 816], [851, 853, 816], [851, 816, 840], [840, 816, 804], [855, 854, 878], [854, 856, 878], [854, 857, 856], [858, 857, 854], [858, 859, 857], [859, 907, 857], [859, 878, 907], [855, 878, 859], [856, 907, 878], [857, 907, 856], [854, 855, 859], [858, 854, 859], [862, 860, 856], [861, 862, 856], [864, 863, 862], [862, 863, 860], [861, 856, 878], [865, 861, 878], [867, 866, 901], [869, 868, 867], [867, 868, 866], [869, 870, 868], [869, 871, 870], [863, 872, 860], [870, 872, 868], [869, 873, 871], [870, 874, 872], [872, 874, 860], [873, 875, 871], [874, 875, 860], [873, 876, 875], [873, 878, 877], [869, 878, 873], [877, 878, 879], [878, 880, 879], [879, 881, 877], [879, 882, 881], [880, 856, 883], [883, 856, 882], [878, 856, 880], [882, 856, 881], [875, 856, 860], [876, 856, 875], [881, 856, 876], [900, 902, 884], [886, 900, 885], [900, 884, 885], [886, 885, 887], [886, 887, 888], [864, 862, 889], [887, 885, 889], [886, 888, 890], [887, 889, 891], [889, 862, 891], [890, 888, 892], [891, 862, 892], [890, 892, 893], [890, 894, 865], [886, 890, 865], [894, 895, 865], [865, 895, 896], [895, 894, 897], [895, 897, 898], [899, 898, 861], [896, 899, 861], [865, 896, 861], [898, 897, 861], [893, 862, 861], [892, 862, 893], [897, 893, 861], [889, 872, 863], [864, 889, 863], [865, 878, 869], [886, 865, 869], [886, 869, 867], [900, 886, 867], [900, 867, 901], [902, 900, 901], [902, 901, 884], [884, 901, 866], [884, 866, 868], [885, 884, 868], [885, 868, 889], [889, 868, 872], [891, 892, 874], [892, 875, 874], [887, 874, 870], [891, 874, 887], [888, 870, 871], [887, 870, 888], [892, 888, 875], [888, 871, 875], [894, 877, 881], [897, 894, 881], [897, 881, 876], [893, 897, 876], [893, 876, 890], [890, 876, 873], [890, 873, 894], [894, 873, 877], [896, 880, 883], [899, 896, 883], [899, 883, 882], [898, 899, 882], [898, 882, 895], [895, 882, 879], [895, 879, 896], [896, 879, 880], [904, 903, 927], [857, 904, 927], [906, 905, 904], [904, 905, 903], [857, 927, 921], [907, 857, 921], [910, 909, 908], [912, 911, 910], [910, 911, 909], [912, 913, 911], [912, 914, 913], [905, 915, 903], [913, 915, 911], [912, 916, 914], [913, 917, 915], [915, 917, 903], [916, 918, 914], [917, 918, 903], [916, 919, 918], [916, 921, 920], [912, 921, 916], [920, 921, 922], [921, 923, 922], [922, 924, 920], [922, 925, 924], [923, 927, 926], [926, 927, 925], [921, 927, 923], [925, 927, 924], [918, 927, 903], [919, 927, 918], [924, 927, 919], [929, 945, 928], [931, 929, 930], [929, 928, 930], [931, 930, 932], [931, 932, 933], [906, 904, 934], [932, 930, 934], [931, 933, 935], [932, 934, 936], [934, 904, 936], [935, 933, 937], [936, 904, 937], [935, 937, 938], [935, 939, 907], [931, 935, 907], [939, 940, 907], [907, 940, 941], [940, 939, 942], [940, 942, 943], [944, 943, 857], [941, 944, 857], [907, 941, 857], [943, 942, 857], [938, 904, 857], [937, 904, 938], [942, 938, 857], [934, 915, 905], [906, 934, 905], [907, 921, 912], [931, 907, 912], [931, 912, 910], [929, 931, 910], [929, 910, 908], [945, 929, 908], [945, 908, 928], [928, 908, 909], [928, 909, 911], [930, 928, 911], [930, 911, 934], [934, 911, 915], [936, 937, 917], [937, 918, 917], [932, 917, 913], [936, 917, 932], [933, 913, 914], [932, 913, 933], [937, 933, 918], [933, 914, 918], [939, 920, 924], [942, 939, 924], [942, 924, 919], [938, 942, 919], [938, 919, 935], [935, 919, 916], [935, 916, 939], [939, 916, 920], [941, 923, 926], [944, 941, 926], [944, 926, 925], [943, 944, 925], [943, 925, 940], [940, 925, 922], [940, 922, 941], [941, 922, 923], [999, 947, 946], [999, 946, 948], [999, 948, 949], [999, 949, 950], [999, 950, 951], [999, 951, 952], [999, 952, 953], [999, 953, 954], [999, 954, 955], [999, 955, 956], [999, 956, 957], [999, 957, 958], [947, 999, 959], [947, 959, 960], [960, 959, 961], [948, 946, 961], [961, 959, 962], [948, 961, 962], [962, 959, 963], [950, 949, 963], [963, 959, 964], [950, 963, 964], [964, 959, 965], [952, 951, 965], [968, 967, 966], [970, 969, 966], [972, 971, 966], [958, 973, 966], [975, 974, 966], [976, 968, 966], [967, 970, 966], [969, 972, 966], [971, 975, 966], [974, 977, 966], [999, 958, 966], [973, 976, 966], [952, 965, 978], [965, 959, 978], [954, 953, 979], [954, 979, 980], [956, 955, 981], [956, 981, 982], [958, 957, 983], [958, 983, 984], [976, 973, 985], [976, 985, 986], [967, 968, 987], [968, 988, 987], [969, 970, 989], [969, 989, 990], [971, 972, 991], [971, 991, 992], [974, 975, 993], [974, 993, 994], [966, 977, 995], [991, 990, 996], [993, 992, 996], [966, 995, 996], [995, 994, 996], [992, 991, 996], [994, 993, 996], [978, 959, 996], [979, 978, 996], [980, 979, 996], [981, 980, 996], [982, 981, 996], [983, 982, 996], [984, 983, 996], [985, 984, 996], [986, 985, 996], [988, 986, 996], [987, 988, 996], [989, 987, 996], [990, 989, 996], [999, 997, 998], [999, 998, 959], [996, 998, 886], [996, 959, 998], [966, 912, 997], [966, 997, 999], [912, 966, 996], [912, 996, 886], [1001, 1000, 949], [1000, 963, 949], [1002, 963, 1000], [1002, 962, 963], [1002, 1003, 962], [1003, 948, 962], [1001, 948, 1003], [1001, 949, 948], [1005, 1004, 953], [1004, 979, 953], [1006, 979, 1004], [1006, 978, 979], [1006, 1007, 978], [1007, 952, 978], [1005, 952, 1007], [1005, 953, 952], [1009, 1008, 957], [1008, 983, 957], [1010, 983, 1008], [1010, 982, 983], [1010, 1011, 982], [1011, 956, 982], [1009, 956, 1011], [1009, 957, 956], [1013, 1012, 973], [1012, 985, 973], [1014, 985, 1012], [1014, 984, 985], [1014, 1015, 984], [1015, 958, 984], [1015, 973, 958], [1013, 973, 1015], [1017, 1016, 968], [1016, 988, 968], [1018, 988, 1016], [1018, 986, 988], [1018, 1019, 986], [1019, 976, 986], [1019, 968, 976], [1017, 968, 1019], [1021, 1020, 970], [1020, 989, 970], [1022, 989, 1020], [1022, 987, 989], [1022, 1023, 987], [1023, 967, 987], [1021, 967, 1023], [1021, 970, 967], [1025, 1024, 972], [1024, 991, 972], [1026, 991, 1024], [1026, 990, 991], [1026, 1027, 990], [1027, 969, 990], [1027, 972, 969], [1025, 972, 1027], [1029, 1028, 975], [1028, 993, 975], [1030, 993, 1028], [1030, 992, 993], [1030, 1031, 992], [1031, 971, 992], [1031, 975, 971], [1029, 975, 1031], [1033, 1032, 977], [1032, 995, 977], [1034, 995, 1032], [1034, 994, 995], [1034, 974, 994], [1035, 974, 1034], [1035, 977, 974], [1033, 977, 1035], [1037, 1036, 946], [1036, 961, 946], [1038, 961, 1036], [1038, 960, 961], [1038, 1039, 960], [1039, 947, 960], [1039, 946, 947], [1037, 946, 1039], [1041, 1040, 951], [1040, 965, 951], [1042, 965, 1040], [1042, 964, 965], [1042, 1043, 964], [1043, 950, 964], [1043, 951, 950], [1041, 951, 1043], [1045, 1044, 955], [1044, 981, 955], [1046, 981, 1044], [1046, 980, 981], [1046, 1047, 980], [1047, 954, 980], [1045, 954, 1047], [1045, 955, 954], [912, 1033, 1035], [912, 1035, 1029], [912, 1029, 1031], [912, 1031, 1025], [912, 1025, 1027], [912, 1027, 1021], [912, 1021, 1023], [912, 1023, 1017], [912, 1017, 1019], [912, 1019, 1013], [912, 1013, 1015], [912, 886, 1032], [1033, 912, 1032], [1032, 886, 1034], [1029, 1035, 1034], [1034, 886, 1028], [1029, 1034, 1028], [1028, 886, 1030], [1030, 886, 1024], [1031, 1030, 1024], [1025, 1031, 1024], [1024, 886, 1026], [1047, 1005, 997], [1007, 1041, 997], [1043, 1001, 997], [1003, 1037, 997], [1005, 1007, 997], [1001, 1003, 997], [1009, 1011, 997], [1045, 1047, 997], [1041, 1043, 997], [1037, 1039, 997], [912, 1015, 997], [1015, 1009, 997], [1011, 1045, 997], [1027, 1026, 1020], [1021, 1027, 1020], [1023, 1022, 1016], [1017, 1023, 1016], [1013, 1019, 1018], [1013, 1018, 1012], [1015, 1014, 1008], [1009, 1015, 1008], [1011, 1010, 1044], [1045, 1011, 1044], [1005, 1047, 1004], [1047, 1046, 1004], [1041, 1007, 1040], [1007, 1006, 1040], [1001, 1043, 1000], [1043, 1042, 1000], [1037, 1003, 1036], [1003, 1002, 1036], [997, 1039, 1038], [1002, 1000, 998], [1042, 1040, 998], [997, 1038, 998], [1040, 1006, 998], [1000, 1042, 998], [1038, 1036, 998], [1036, 1002, 998], [1046, 886, 998], [1044, 886, 1046], [1010, 886, 1044], [1008, 886, 1010], [1014, 886, 1008], [1012, 886, 1014], [1018, 886, 1012], [1016, 886, 1018], [1022, 886, 1016], [1020, 886, 1022], [1026, 886, 1020], [1004, 1046, 998], [1006, 1004, 998], [1050, 1049, 1048], [1048, 1049, 1051], [1127, 1052, 1049], [1050, 1127, 1049], [1048, 1051, 1053], [1054, 1048, 1053], [1051, 1056, 1055], [1051, 1057, 1056], [1051, 1058, 1057], [1051, 1059, 1058], [1051, 1060, 1059], [1051, 1061, 1060], [1051, 1062, 1061], [1051, 1063, 1062], [1055, 1064, 1051], [1057, 1065, 1056], [1065, 1053, 1064], [1064, 1053, 1051], [1057, 1066, 1065], [1065, 1066, 1053], [1066, 1067, 1053], [1069, 1049, 1068], [1063, 1049, 1070], [1072, 1049, 1071], [1074, 1049, 1073], [1051, 1049, 1063], [1070, 1049, 1072], [1071, 1049, 1069], [1068, 1049, 1074], [1058, 1075, 1067], [1059, 1075, 1058], [1067, 1075, 1053], [1075, 1076, 1053], [1049, 1077, 1073], [1049, 1078, 1077], [1060, 1079, 1076], [1061, 1079, 1060], [1076, 1079, 1053], [1079, 1080, 1053], [1062, 1081, 1080], [1080, 1081, 1053], [1063, 1081, 1062], [1070, 1083, 1082], [1072, 1083, 1070], [1071, 1085, 1084], [1069, 1085, 1071], [1068, 1087, 1086], [1074, 1087, 1068], [1077, 1088, 1073], [1077, 1089, 1088], [1086, 1052, 1085], [1089, 1052, 1088], [1084, 1052, 1083], [1088, 1052, 1087], [1090, 1052, 1089], [1078, 1052, 1090], [1083, 1052, 1082], [1049, 1052, 1078], [1085, 1052, 1084], [1087, 1052, 1086], [1081, 1052, 1053], [1082, 1052, 1081], [1048, 1092, 1091], [1048, 1091, 1093], [1048, 1093, 1094], [1048, 1094, 1095], [1048, 1095, 1096], [1048, 1096, 1097], [1048, 1097, 1098], [1048, 1098, 1099], [1092, 1048, 1100], [1093, 1091, 1101], [1101, 1100, 1054], [1100, 1048, 1054], [1093, 1101, 1102], [1101, 1054, 1102], [1102, 1054, 1103], [1099, 1104, 1050], [1106, 1105, 1050], [1108, 1107, 1050], [1110, 1109, 1050], [1048, 1099, 1050], [1104, 1106, 1050], [1105, 1108, 1050], [1107, 1110, 1050], [1094, 1103, 1111], [1095, 1094, 1111], [1103, 1054, 1111], [1111, 1054, 1112], [1050, 1109, 1113], [1050, 1113, 1114], [1096, 1112, 1115], [1097, 1096, 1115], [1112, 1054, 1115], [1115, 1054, 1116], [1116, 1054, 1117], [1098, 1116, 1117], [1099, 1098, 1117], [1104, 1119, 1118], [1106, 1104, 1118], [1105, 1121, 1120], [1108, 1105, 1120], [1110, 1107, 1122], [1110, 1122, 1123], [1109, 1125, 1124], [1113, 1109, 1124], [1050, 1114, 1126], [1120, 1121, 1127], [1123, 1122, 1127], [1124, 1125, 1127], [1050, 1126, 1127], [1121, 1118, 1127], [1122, 1120, 1127], [1125, 1123, 1127], [1126, 1124, 1127], [1118, 1119, 1127], [1119, 1054, 1127], [1117, 1054, 1119], [1054, 1053, 1127], [1127, 1053, 1052], [1107, 1108, 1069], [1107, 1069, 1068], [1107, 1068, 1122], [1122, 1068, 1086], [1122, 1086, 1085], [1120, 1122, 1085], [1120, 1085, 1108], [1108, 1085, 1069], [1101, 1056, 1065], [1091, 1056, 1101], [1101, 1064, 1100], [1101, 1065, 1064], [1092, 1064, 1055], [1100, 1064, 1092], [1091, 1055, 1056], [1092, 1055, 1091], [1103, 1058, 1067], [1094, 1058, 1103], [1103, 1066, 1102], [1103, 1067, 1066], [1093, 1066, 1057], [1102, 1066, 1093], [1094, 1057, 1058], [1093, 1057, 1094], [1112, 1060, 1076], [1096, 1060, 1112], [1112, 1075, 1111], [1112, 1076, 1075], [1095, 1075, 1059], [1111, 1075, 1095], [1095, 1060, 1096], [1095, 1059, 1060], [1116, 1098, 1080], [1098, 1062, 1080], [1115, 1080, 1079], [1116, 1080, 1115], [1097, 1079, 1061], [1115, 1079, 1097], [1097, 1062, 1098], [1097, 1061, 1062], [1119, 1104, 1082], [1104, 1070, 1082], [1117, 1119, 1081], [1119, 1082, 1081], [1099, 1117, 1063], [1117, 1081, 1063], [1104, 1063, 1070], [1099, 1063, 1104], [1121, 1105, 1084], [1105, 1071, 1084], [1121, 1083, 1118], [1121, 1084, 1083], [1106, 1118, 1072], [1118, 1083, 1072], [1106, 1071, 1105], [1106, 1072, 1071], [1125, 1109, 1088], [1109, 1073, 1088], [1125, 1087, 1123], [1125, 1088, 1087], [1110, 1123, 1074], [1123, 1087, 1074], [1109, 1074, 1073], [1110, 1074, 1109], [1114, 1113, 1077], [1114, 1077, 1078], [1114, 1078, 1126], [1126, 1078, 1090], [1126, 1090, 1089], [1124, 1126, 1089], [1124, 1089, 1113], [1113, 1089, 1077], [1129, 1128, 1148], [1128, 1130, 1148], [1128, 1216, 1130], [1131, 1216, 1128], [1131, 1132, 1216], [1132, 1181, 1216], [1132, 1148, 1181], [1129, 1148, 1132], [1130, 1181, 1148], [1216, 1181, 1130], [1128, 1129, 1132], [1131, 1128, 1132], [1134, 1133, 1130], [1170, 1134, 1130], [1136, 1135, 1134], [1134, 1135, 1133], [1170, 1130, 1148], [1164, 1170, 1148], [1138, 1137, 1175], [1172, 1139, 1138], [1138, 1139, 1137], [1172, 1140, 1139], [1172, 1141, 1140], [1135, 1142, 1133], [1140, 1142, 1139], [1172, 1143, 1141], [1140, 1144, 1142], [1142, 1144, 1133], [1143, 1145, 1141], [1144, 1145, 1133], [1143, 1146, 1145], [1143, 1148, 1147], [1172, 1148, 1143], [1147, 1148, 1149], [1148, 1150, 1149], [1149, 1151, 1147], [1149, 1152, 1151], [1150, 1130, 1153], [1153, 1130, 1152], [1148, 1130, 1150], [1152, 1130, 1151], [1145, 1130, 1133], [1146, 1130, 1145], [1151, 1130, 1146], [1174, 1155, 1154], [1173, 1174, 1156], [1174, 1154, 1156], [1173, 1156, 1157], [1173, 1157, 1158], [1136, 1134, 1159], [1157, 1156, 1159], [1173, 1158, 1160], [1157, 1159, 1161], [1159, 1134, 1161], [1160, 1158, 1162], [1161, 1134, 1162], [1160, 1162, 1163], [1160, 1165, 1164], [1173, 1160, 1164], [1165, 1166, 1164], [1164, 1166, 1167], [1166, 1165, 1168], [1166, 1168, 1169], [1171, 1169, 1170], [1167, 1171, 1170], [1164, 1167, 1170], [1169, 1168, 1170], [1163, 1134, 1170], [1162, 1134, 1163], [1168, 1163, 1170], [1159, 1142, 1135], [1136, 1159, 1135], [1164, 1148, 1172], [1173, 1164, 1172], [1173, 1172, 1138], [1174, 1173, 1138], [1174, 1138, 1175], [1155, 1174, 1175], [1155, 1175, 1154], [1154, 1175, 1137], [1154, 1137, 1139], [1156, 1154, 1139], [1156, 1139, 1159], [1159, 1139, 1142], [1161, 1162, 1144], [1162, 1145, 1144], [1157, 1144, 1140], [1161, 1144, 1157], [1158, 1140, 1141], [1157, 1140, 1158], [1162, 1158, 1145], [1158, 1141, 1145], [1165, 1147, 1151], [1168, 1165, 1151], [1168, 1151, 1146], [1163, 1168, 1146], [1163, 1146, 1160], [1160, 1146, 1143], [1160, 1143, 1165], [1165, 1143, 1147], [1167, 1150, 1153], [1171, 1167, 1153], [1171, 1153, 1152], [1169, 1171, 1152], [1169, 1152, 1166], [1166, 1152, 1149], [1166, 1149, 1167], [1167, 1149, 1150], [1177, 1176, 1198], [1216, 1177, 1198], [1179, 1178, 1177], [1177, 1178, 1176], [1216, 1198, 1180], [1181, 1216, 1180], [1183, 1182, 1218], [1269, 1184, 1183], [1183, 1184, 1182], [1269, 1185, 1184], [1269, 1186, 1185], [1178, 1187, 1176], [1185, 1187, 1184], [1269, 1188, 1186], [1185, 1189, 1187], [1187, 1189, 1176], [1188, 1190, 1186], [1189, 1190, 1176], [1188, 1191, 1190], [1188, 1180, 1192], [1269, 1180, 1188], [1192, 1180, 1193], [1180, 1194, 1193], [1193, 1195, 1192], [1193, 1196, 1195], [1194, 1198, 1197], [1197, 1198, 1196], [1180, 1198, 1194], [1196, 1198, 1195], [1190, 1198, 1176], [1191, 1198, 1190], [1195, 1198, 1191], [1201, 1200, 1199], [1203, 1201, 1202], [1201, 1199, 1202], [1203, 1202, 1204], [1203, 1204, 1205], [1179, 1177, 1206], [1204, 1202, 1206], [1203, 1205, 1207], [1204, 1206, 1208], [1206, 1177, 1208], [1207, 1205, 1209], [1208, 1177, 1209], [1207, 1209, 1210], [1207, 1211, 1181], [1203, 1207, 1181], [1211, 1212, 1181], [1181, 1212, 1213], [1212, 1211, 1214], [1212, 1214, 1215], [1217, 1215, 1216], [1213, 1217, 1216], [1181, 1213, 1216], [1215, 1214, 1216], [1210, 1177, 1216], [1209, 1177, 1210], [1214, 1210, 1216], [1206, 1187, 1178], [1179, 1206, 1178], [1181, 1180, 1269], [1203, 1181, 1269], [1203, 1269, 1183], [1201, 1203, 1183], [1201, 1183, 1218], [1200, 1201, 1218], [1200, 1218, 1199], [1199, 1218, 1182], [1199, 1182, 1184], [1202, 1199, 1184], [1202, 1184, 1206], [1206, 1184, 1187], [1208, 1209, 1189], [1209, 1190, 1189], [1204, 1189, 1185], [1208, 1189, 1204], [1205, 1185, 1186], [1204, 1185, 1205], [1209, 1205, 1190], [1205, 1186, 1190], [1211, 1192, 1195], [1214, 1211, 1195], [1214, 1195, 1191], [1210, 1214, 1191], [1210, 1191, 1207], [1207, 1191, 1188], [1207, 1188, 1211], [1211, 1188, 1192], [1213, 1194, 1197], [1217, 1213, 1197], [1217, 1197, 1196], [1215, 1217, 1196], [1215, 1196, 1212], [1212, 1196, 1193], [1212, 1193, 1213], [1213, 1193, 1194], [1270, 1220, 1219], [1270, 1219, 1221], [1270, 1221, 1222], [1270, 1222, 1223], [1270, 1223, 1224], [1270, 1224, 1225], [1270, 1225, 1226], [1270, 1226, 1227], [1270, 1227, 1228], [1270, 1228, 1229], [1270, 1229, 1230], [1270, 1230, 1231], [1220, 1270, 1268], [1220, 1268, 1232], [1232, 1268, 1233], [1221, 1219, 1233], [1233, 1268, 1234], [1221, 1233, 1234], [1234, 1268, 1235], [1223, 1222, 1235], [1235, 1268, 1236], [1223, 1235, 1236], [1236, 1268, 1237], [1225, 1224, 1237], [1239, 1238, 1272], [1241, 1240, 1272], [1243, 1242, 1272], [1231, 1244, 1272], [1246, 1245, 1272], [1247, 1239, 1272], [1238, 1241, 1272], [1240, 1243, 1272], [1242, 1246, 1272], [1245, 1248, 1272], [1270, 1231, 1272], [1244, 1247, 1272], [1225, 1237, 1249], [1237, 1268, 1249], [1227, 1226, 1250], [1227, 1250, 1251], [1229, 1228, 1252], [1229, 1252, 1253], [1231, 1230, 1254], [1231, 1254, 1255], [1247, 1244, 1256], [1247, 1256, 1257], [1238, 1239, 1258], [1239, 1259, 1258], [1240, 1241, 1260], [1240, 1260, 1261], [1242, 1243, 1262], [1242, 1262, 1263], [1245, 1246, 1264], [1245, 1264, 1265], [1272, 1248, 1266], [1262, 1261, 1271], [1264, 1263, 1271], [1272, 1266, 1271], [1266, 1265, 1271], [1263, 1262, 1271], [1265, 1264, 1271], [1249, 1268, 1271], [1250, 1249, 1271], [1251, 1250, 1271], [1252, 1251, 1271], [1253, 1252, 1271], [1254, 1253, 1271], [1255, 1254, 1271], [1256, 1255, 1271], [1257, 1256, 1271], [1259, 1257, 1271], [1258, 1259, 1271], [1260, 1258, 1271], [1261, 1260, 1271], [1270, 1321, 1267], [1270, 1267, 1268], [1271, 1267, 1173], [1271, 1268, 1267], [1272, 1269, 1321], [1272, 1321, 1270], [1269, 1272, 1271], [1269, 1271, 1173], [1274, 1273, 1222], [1273, 1235, 1222], [1275, 1235, 1273], [1275, 1234, 1235], [1275, 1276, 1234], [1276, 1221, 1234], [1274, 1221, 1276], [1274, 1222, 1221], [1278, 1277, 1226], [1277, 1250, 1226], [1279, 1250, 1277], [1279, 1249, 1250], [1279, 1280, 1249], [1280, 1225, 1249], [1278, 1225, 1280], [1278, 1226, 1225], [1282, 1281, 1230], [1281, 1254, 1230], [1283, 1254, 1281], [1283, 1253, 1254], [1283, 1284, 1253], [1284, 1229, 1253], [1282, 1229, 1284], [1282, 1230, 1229], [1286, 1285, 1244], [1285, 1256, 1244], [1287, 1256, 1285], [1287, 1255, 1256], [1287, 1288, 1255], [1288, 1231, 1255], [1288, 1244, 1231], [1286, 1244, 1288], [1290, 1289, 1239], [1289, 1259, 1239], [1291, 1259, 1289], [1291, 1257, 1259], [1291, 1292, 1257], [1292, 1247, 1257], [1292, 1239, 1247], [1290, 1239, 1292], [1294, 1293, 1241], [1293, 1260, 1241], [1295, 1260, 1293], [1295, 1258, 1260], [1295, 1296, 1258], [1296, 1238, 1258], [1294, 1238, 1296], [1294, 1241, 1238], [1298, 1297, 1243], [1297, 1262, 1243], [1299, 1262, 1297], [1299, 1261, 1262], [1299, 1300, 1261], [1300, 1240, 1261], [1300, 1243, 1240], [1298, 1243, 1300], [1302, 1301, 1246], [1301, 1264, 1246], [1303, 1264, 1301], [1303, 1263, 1264], [1303, 1304, 1263], [1304, 1242, 1263], [1304, 1246, 1242], [1302, 1246, 1304], [1306, 1305, 1248], [1305, 1266, 1248], [1307, 1266, 1305], [1307, 1265, 1266], [1307, 1245, 1265], [1308, 1245, 1307], [1308, 1248, 1245], [1306, 1248, 1308], [1310, 1309, 1219], [1309, 1233, 1219], [1311, 1233, 1309], [1311, 1232, 1233], [1311, 1312, 1232], [1312, 1220, 1232], [1312, 1219, 1220], [1310, 1219, 1312], [1314, 1313, 1224], [1313, 1237, 1224], [1315, 1237, 1313], [1315, 1236, 1237], [1315, 1316, 1236], [1316, 1223, 1236], [1316, 1224, 1223], [1314, 1224, 1316], [1318, 1317, 1228], [1317, 1252, 1228], [1319, 1252, 1317], [1319, 1251, 1252], [1319, 1320, 1251], [1320, 1227, 1251], [1318, 1227, 1320], [1318, 1228, 1227], [1269, 1306, 1308], [1269, 1308, 1302], [1269, 1302, 1304], [1269, 1304, 1298], [1269, 1298, 1300], [1269, 1300, 1294], [1269, 1294, 1296], [1269, 1296, 1290], [1269, 1290, 1292], [1269, 1292, 1286], [1269, 1286, 1288], [1269, 1173, 1305], [1306, 1269, 1305], [1305, 1173, 1307], [1302, 1308, 1307], [1307, 1173, 1301], [1302, 1307, 1301], [1301, 1173, 1303], [1303, 1173, 1297], [1304, 1303, 1297], [1298, 1304, 1297], [1297, 1173, 1299], [1320, 1278, 1321], [1280, 1314, 1321], [1316, 1274, 1321], [1276, 1310, 1321], [1278, 1280, 1321], [1274, 1276, 1321], [1282, 1284, 1321], [1318, 1320, 1321], [1314, 1316, 1321], [1310, 1312, 1321], [1269, 1288, 1321], [1288, 1282, 1321], [1284, 1318, 1321], [1300, 1299, 1293], [1294, 1300, 1293], [1296, 1295, 1289], [1290, 1296, 1289], [1286, 1292, 1291], [1286, 1291, 1285], [1288, 1287, 1281], [1282, 1288, 1281], [1284, 1283, 1317], [1318, 1284, 1317], [1278, 1320, 1277], [1320, 1319, 1277], [1314, 1280, 1313], [1280, 1279, 1313], [1274, 1316, 1273], [1316, 1315, 1273], [1310, 1276, 1309], [1276, 1275, 1309], [1321, 1312, 1311], [1275, 1273, 1267], [1315, 1313, 1267], [1321, 1311, 1267], [1313, 1279, 1267], [1273, 1315, 1267], [1311, 1309, 1267], [1309, 1275, 1267], [1319, 1173, 1267], [1317, 1173, 1319], [1283, 1173, 1317], [1281, 1173, 1283], [1287, 1173, 1281], [1285, 1173, 1287], [1291, 1173, 1285], [1289, 1173, 1291], [1295, 1173, 1289], [1293, 1173, 1295], [1299, 1173, 1293], [1277, 1319, 1267], [1279, 1277, 1267], [1324, 1323, 1322], [1323, 1325, 1322], [1323, 1326, 1325], [1327, 1326, 1323], [1327, 1328, 1326], [1329, 1328, 1327], [1329, 1330, 1328], [1330, 1331, 1328], [1330, 1332, 1331], [1333, 1332, 1330], [1333, 1334, 1332], [1335, 1334, 1333], [1335, 1336, 1334], [1336, 1337, 1334], [1336, 1324, 1337], [1324, 1322, 1337], [1325, 1337, 1322], [1326, 1337, 1325], [1326, 1328, 1337], [1328, 1334, 1337], [1328, 1331, 1334], [1331, 1332, 1334], [1323, 1324, 1336], [1327, 1323, 1336], [1327, 1336, 1329], [1329, 1336, 1335], [1329, 1335, 1330], [1330, 1335, 1333]], "pts": [[2.609, 3.192, 0.282], [2.609, 3.173, 0.811], [2.609, 3.192, 0.811], [2.609, 3.173, 0.282], [3.094, 3.173, 0.811], [3.094, 3.192, 0.811], [2.709, 3.173, 0.338], [2.709, 3.173, 0.353], [2.709, 3.173, 0.383], [2.709, 3.173, 0.398], [2.709, 3.173, 0.428], [2.709, 3.173, 0.443], [2.709, 3.173, 0.473], [2.709, 3.173, 0.488], [2.709, 3.173, 0.518], [2.994, 3.173, 0.338], [2.994, 3.173, 0.353], [3.094, 3.173, 0.282], [2.994, 3.173, 0.383], [2.994, 3.173, 0.398], [2.709, 3.173, 0.665], [2.709, 3.173, 0.65], [2.709, 3.173, 0.575], [2.709, 3.173, 0.62], [2.709, 3.173, 0.605], [2.709, 3.173, 0.71], [2.709, 3.173, 0.695], [2.994, 3.173, 0.428], [2.994, 3.173, 0.443], [2.709, 3.173, 0.74], [2.709, 3.173, 0.755], [2.994, 3.173, 0.473], [2.994, 3.173, 0.488], [2.994, 3.173, 0.518], [2.994, 3.173, 0.575], [2.994, 3.173, 0.605], [2.994, 3.173, 0.62], [2.994, 3.173, 0.65], [2.994, 3.173, 0.665], [2.994, 3.173, 0.695], [2.994, 3.173, 0.71], [2.994, 3.173, 0.74], [2.994, 3.173, 0.755], [2.709, 3.192, 0.353], [2.709, 3.192, 0.338], [2.709, 3.192, 0.383], [2.709, 3.192, 0.398], [2.709, 3.192, 0.428], [2.709, 3.192, 0.443], [2.709, 3.192, 0.473], [2.709, 3.192, 0.488], [2.709, 3.192, 0.518], [2.994, 3.192, 0.338], [2.994, 3.192, 0.353], [3.094, 3.192, 0.282], [2.994, 3.192, 0.383], [2.994, 3.192, 0.398], [2.709, 3.192, 0.575], [2.709, 3.192, 0.62], [2.709, 3.192, 0.605], [2.709, 3.192, 0.665], [2.709, 3.192, 0.65], [2.709, 3.192, 0.71], [2.709, 3.192, 0.695], [2.994, 3.192, 0.428], [2.994, 3.192, 0.443], [2.709, 3.192, 0.74], [2.709, 3.192, 0.755], [2.994, 3.192, 0.473], [2.994, 3.192, 0.488], [2.994, 3.192, 0.518], [2.994, 3.192, 0.605], [2.994, 3.192, 0.575], [2.994, 3.192, 0.65], [2.994, 3.192, 0.62], [2.994, 3.192, 0.665], [2.994, 3.192, 0.695], [2.994, 3.192, 0.74], [2.994, 3.192, 0.71], [2.994, 3.192, 0.755], [2.588, 3.173, 0.925], [2.588, 3.716, 0.925], [2.588, 3.173, 0.906], [3.115, 3.173, 0.925], [3.115, 3.716, 0.925], [2.588, 3.173, 0.0], [2.607, 3.173, 0.906], [2.607, 3.173, 0.0], [2.588, 3.293, -0.0], [2.607, 3.293, -0.0], [2.588, 3.716, 0.906], [2.607, 3.716, 0.906], [2.588, 3.726, -0.0], [2.588, 3.626, -0.0], [2.588, 3.604364258, 0.17741247599999999], [2.588, 3.592258789, 0.276677063], [2.588, 3.580153564, 0.37594164999999996], [2.588, 3.293, 0.160060806], [2.588, 3.568048096, 0.475206238], [2.588, 3.293, 0.26], [2.588, 3.293, 0.359939209], [2.588, 3.293, 0.459878387], [2.588, 3.555942627, 0.574470825], [2.588, 3.5438371579999997, 0.673735413], [2.588, 3.5317316890000003, 0.773], [2.588, 3.293, 0.5598175660000001], [2.588, 3.293, 0.659756775], [2.588, 3.293, 0.773], [2.607, 3.626, -0.0], [2.607, 3.726, -0.0], [2.607, 3.716984863, 0.07392497299999999], [2.607, 3.604364258, 0.17741247599999999], [2.607, 3.592258789, 0.276677063], [2.607, 3.580153564, 0.37594164999999996], [2.607, 3.293, 0.160060806], [2.607, 3.568048096, 0.475206238], [2.607, 3.293, 0.26], [2.607, 3.293, 0.359939209], [2.607, 3.293, 0.459878387], [2.607, 3.555942627, 0.574470825], [2.607, 3.5438371579999997, 0.673735413], [2.607, 3.5317316890000003, 0.773], [2.607, 3.293, 0.5598175660000001], [2.607, 3.293, 0.659756775], [2.607, 3.293, 0.773], [2.588, 3.816, 0.086], [2.588, 3.716984863, 0.07392497299999999], [3.096, 3.173, 0.0], [3.115, 3.173, 0.0], [3.096, 3.293, -0.0], [3.096, 3.716, 0.906], [3.096, 3.726, -0.0], [3.096, 3.626, -0.0], [3.096, 3.716984863, 0.07392497299999999], [3.096, 3.604364258, 0.17741247599999999], [3.096, 3.816, 0.086], [3.096, 3.592258789, 0.276677063], [3.096, 3.580153564, 0.37594164999999996], [3.096, 3.293, 0.160060806], [3.096, 3.568048096, 0.475206238], [3.096, 3.293, 0.26], [3.096, 3.293, 0.359939209], [3.096, 3.293, 0.459878387], [3.096, 3.555942627, 0.574470825], [3.096, 3.5438371579999997, 0.673735413], [3.096, 3.5317316890000003, 0.773], [3.096, 3.293, 0.5598175660000001], [3.096, 3.293, 0.659756775], [3.096, 3.293, 0.773], [3.096, 3.173, 0.906], [3.115, 3.726, -0.0], [3.115, 3.604364258, 0.17741247599999999], [3.115, 3.568048096, 0.475206238], [3.115, 3.293, 0.359939209], [3.115, 3.293, 0.459878387], [3.115, 3.716, 0.906], [3.115, 3.555942627, 0.574470825], [3.115, 3.5438371579999997, 0.673735413], [3.115, 3.5317316890000003, 0.773], [3.115, 3.293, 0.5598175660000001], [3.115, 3.293, 0.659756775], [3.115, 3.173, 0.906], [3.115, 3.293, 0.773], [3.039, 3.277671143, 0.03949091], [3.039, 3.2677446289999996, 0.038280365000000004], [3.039, 3.307450684, 0.04312254], [3.039, 3.317376953, 0.044333084], [3.039, 3.347156494, 0.047964714], [3.039, 3.3570827640000003, 0.049175259], [3.039, 3.386862305, 0.052806888999999996], [3.039, 3.396788818, 0.054017433], [3.039, 3.4265681150000002, 0.057649067000000005], [3.039, 3.436494629, 0.058859608], [3.039, 3.466273926, 0.062491241], [3.039, 3.4762004390000003, 0.063701782], [3.039, 3.5059797359999996, 0.067333412], [2.664, 3.2677446289999996, 0.038280365000000004], [2.664, 3.277671143, 0.03949091], [2.664, 3.307450684, 0.04312254], [2.664, 3.317376953, 0.044333084], [2.664, 3.347156494, 0.047964714], [2.664, 3.3570827640000003, 0.049175259], [3.096, 3.8136999510000003, 0.104860275], [3.039, 3.585391357, 0.077017769], [3.039, 3.5556120609999997, 0.07338613100000001], [3.039, 3.625097168, 0.08185993999999999], [3.039, 3.595317871, 0.07822831], [3.039, 3.664802979, 0.086702118], [3.039, 3.635023682, 0.08307048], [3.039, 3.51590625, 0.068543961], [3.039, 3.704509033, 0.091544289], [3.039, 3.674729492, 0.087912659], [3.039, 3.545685547, 0.072175591], [3.039, 3.714435303, 0.09275483699999999], [2.664, 3.386862305, 0.052806888999999996], [2.664, 3.396788818, 0.054017433], [2.664, 3.4265681150000002, 0.057649067000000005], [2.664, 3.436494629, 0.058859608], [2.664, 3.466273926, 0.062491241], [2.664, 3.4762004390000003, 0.063701782], [2.664, 3.5059797359999996, 0.067333412], [2.664, 3.51590625, 0.068543961], [2.664, 3.545685547, 0.072175591], [2.664, 3.585391357, 0.077017769], [2.664, 3.5556120609999997, 0.07338613100000001], [2.664, 3.595317871, 0.07822831], [2.664, 3.625097168, 0.08185993999999999], [2.664, 3.635023682, 0.08307048], [2.664, 3.664802979, 0.086702118], [2.664, 3.674729492, 0.087912659], [2.664, 3.704509033, 0.091544289], [2.664, 3.714435303, 0.09275483699999999], [3.096, 3.1943000489999998, 0.010182931], [3.096, 3.192, 0.029043202999999997], [2.607, 3.192, 0.029043202999999997], [2.607, 3.1943000489999998, 0.010182931], [2.607, 3.8136999510000003, 0.104860275], [2.607, 3.816, 0.086], [2.664, 3.3196770019999997, 0.025472812999999997], [3.039, 3.3196770019999997, 0.025472812999999997], [2.664, 3.309750488, 0.024262268], [3.039, 3.309750488, 0.024262268], [2.664, 3.399088623, 0.035157162], [3.039, 3.399088623, 0.035157162], [2.664, 3.389162354, 0.033946617000000005], [3.039, 3.389162354, 0.033946617000000005], [2.664, 3.4785004880000003, 0.044841510999999994], [3.039, 3.4785004880000003, 0.044841510999999994], [2.664, 3.468573975, 0.04363097], [3.039, 3.468573975, 0.04363097], [2.664, 3.518206299, 0.049683689], [3.039, 3.518206299, 0.049683689], [2.664, 3.508279785, 0.048473145], [3.039, 3.508279785, 0.048473145], [2.664, 3.5579121089999997, 0.054525864], [3.039, 3.5579121089999997, 0.054525864], [2.664, 3.547985596, 0.053315319], [3.039, 3.547985596, 0.053315319], [2.664, 3.5976179200000002, 0.059368038], [3.039, 3.5976179200000002, 0.059368038], [2.664, 3.587691406, 0.058157494], [3.039, 3.587691406, 0.058157494], [2.664, 3.63732373, 0.064210213], [3.039, 3.63732373, 0.064210213], [2.664, 3.6273972170000004, 0.062999668], [3.039, 3.6273972170000004, 0.062999668], [2.664, 3.677029541, 0.069052391], [3.039, 3.677029541, 0.069052391], [2.664, 3.667103027, 0.067841843], [3.039, 3.667103027, 0.067841843], [2.664, 3.716735352, 0.073894562], [3.039, 3.716735352, 0.073894562], [2.664, 3.7068088379999997, 0.072684021], [3.039, 3.7068088379999997, 0.072684021], [2.664, 3.279971191, 0.020630638], [3.039, 3.279971191, 0.020630638], [2.664, 3.270044678, 0.019420094], [3.039, 3.270044678, 0.019420094], [2.664, 3.3593828119999998, 0.030314986999999998], [3.039, 3.3593828119999998, 0.030314986999999998], [2.664, 3.3494565429999996, 0.029104445], [3.039, 3.3494565429999996, 0.029104445], [2.664, 3.4387946780000003, 0.039999335999999996], [3.039, 3.4387946780000003, 0.039999335999999996], [2.664, 3.428868164, 0.038788795], [3.039, 3.428868164, 0.038788795], [3.136, 3.192, 0.282], [3.136, 3.173, 0.811], [3.136, 3.192, 0.811], [3.136, 3.173, 0.282], [3.621, 3.173, 0.811], [3.621, 3.173, 0.282], [3.236, 3.173, 0.338], [3.236, 3.173, 0.353], [3.236, 3.173, 0.383], [3.236, 3.173, 0.398], [3.236, 3.173, 0.428], [3.236, 3.173, 0.443], [3.236, 3.173, 0.473], [3.236, 3.173, 0.488], [3.236, 3.173, 0.518], [3.521, 3.173, 0.338], [3.521, 3.173, 0.353], [3.521, 3.173, 0.383], [3.521, 3.173, 0.398], [3.236, 3.173, 0.665], [3.236, 3.173, 0.65], [3.236, 3.173, 0.575], [3.236, 3.173, 0.62], [3.236, 3.173, 0.605], [3.236, 3.173, 0.71], [3.236, 3.173, 0.695], [3.521, 3.173, 0.428], [3.521, 3.173, 0.443], [3.236, 3.173, 0.74], [3.236, 3.173, 0.755], [3.521, 3.173, 0.473], [3.521, 3.173, 0.488], [3.521, 3.173, 0.518], [3.521, 3.173, 0.575], [3.521, 3.173, 0.605], [3.521, 3.173, 0.62], [3.521, 3.173, 0.65], [3.521, 3.173, 0.665], [3.521, 3.173, 0.695], [3.521, 3.173, 0.71], [3.521, 3.173, 0.74], [3.521, 3.173, 0.755], [3.236, 3.192, 0.353], [3.236, 3.192, 0.338], [3.236, 3.192, 0.383], [3.236, 3.192, 0.398], [3.236, 3.192, 0.428], [3.236, 3.192, 0.443], [3.236, 3.192, 0.473], [3.236, 3.192, 0.488], [3.236, 3.192, 0.518], [3.521, 3.192, 0.338], [3.521, 3.192, 0.353], [3.621, 3.192, 0.282], [3.521, 3.192, 0.383], [3.521, 3.192, 0.398], [3.236, 3.192, 0.575], [3.236, 3.192, 0.62], [3.236, 3.192, 0.605], [3.236, 3.192, 0.665], [3.236, 3.192, 0.65], [3.236, 3.192, 0.71], [3.236, 3.192, 0.695], [3.521, 3.192, 0.428], [3.521, 3.192, 0.443], [3.236, 3.192, 0.74], [3.236, 3.192, 0.755], [3.521, 3.192, 0.473], [3.521, 3.192, 0.488], [3.521, 3.192, 0.518], [3.521, 3.192, 0.605], [3.521, 3.192, 0.575], [3.521, 3.192, 0.65], [3.521, 3.192, 0.62], [3.521, 3.192, 0.665], [3.521, 3.192, 0.695], [3.521, 3.192, 0.74], [3.521, 3.192, 0.71], [3.521, 3.192, 0.755], [3.621, 3.192, 0.811], [3.642, 3.173, 0.925], [3.642, 3.716, 0.925], [3.134, 3.173, 0.0], [3.115, 3.293, -0.0], [3.134, 3.293, -0.0], [3.134, 3.716, 0.906], [3.115, 3.626, -0.0], [3.115, 3.592258789, 0.276677063], [3.115, 3.580153564, 0.37594164999999996], [3.115, 3.293, 0.160060806], [3.115, 3.293, 0.26], [3.134, 3.626, -0.0], [3.134, 3.604364258, 0.17741247599999999], [3.134, 3.592258789, 0.276677063], [3.134, 3.580153564, 0.37594164999999996], [3.134, 3.293, 0.160060806], [3.134, 3.568048096, 0.475206238], [3.134, 3.293, 0.26], [3.134, 3.293, 0.359939209], [3.134, 3.293, 0.459878387], [3.134, 3.555942627, 0.574470825], [3.134, 3.5438371579999997, 0.673735413], [3.134, 3.5317316890000003, 0.773], [3.134, 3.293, 0.5598175660000001], [3.134, 3.293, 0.659756775], [3.134, 3.173, 0.906], [3.134, 3.293, 0.773], [3.115, 3.816, 0.086], [3.115, 3.716984863, 0.07392497299999999], [3.134, 3.716984863, 0.07392497299999999], [3.134, 3.726, -0.0], [3.623, 3.173, 0.906], [3.623, 3.173, 0.0], [3.642, 3.173, 0.0], [3.623, 3.293, -0.0], [3.642, 3.716, 0.906], [3.623, 3.626, -0.0], [3.623, 3.604364258, 0.17741247599999999], [3.623, 3.592258789, 0.276677063], [3.623, 3.580153564, 0.37594164999999996], [3.623, 3.293, 0.160060806], [3.623, 3.568048096, 0.475206238], [3.623, 3.293, 0.26], [3.623, 3.293, 0.359939209], [3.623, 3.293, 0.459878387], [3.623, 3.555942627, 0.574470825], [3.623, 3.716, 0.906], [3.623, 3.5438371579999997, 0.673735413], [3.623, 3.5317316890000003, 0.773], [3.623, 3.293, 0.5598175660000001], [3.623, 3.293, 0.659756775], [3.623, 3.293, 0.773], [3.642, 3.726, -0.0], [3.642, 3.716984863, 0.07392497299999999], [3.642, 3.604364258, 0.17741247599999999], [3.642, 3.592258789, 0.276677063], [3.642, 3.580153564, 0.37594164999999996], [3.642, 3.293, 0.26], [3.642, 3.555942627, 0.574470825], [3.642, 3.5438371579999997, 0.673735413], [3.642, 3.5317316890000003, 0.773], [3.642, 3.293, 0.5598175660000001], [3.642, 3.293, 0.659756775], [3.642, 3.173, 0.906], [3.642, 3.293, 0.773], [3.623, 3.716984863, 0.07392497299999999], [3.623, 3.726, -0.0], [3.566, 3.277671143, 0.03949091], [3.566, 3.2677446289999996, 0.038280365000000004], [3.566, 3.307450684, 0.04312254], [3.566, 3.317376953, 0.044333084], [3.566, 3.347156494, 0.047964714], [3.566, 3.3570827640000003, 0.049175259], [3.566, 3.386862305, 0.052806888999999996], [3.566, 3.396788818, 0.054017433], [3.566, 3.4265681150000002, 0.057649067000000005], [3.566, 3.436494629, 0.058859608], [3.566, 3.466273926, 0.062491241], [3.566, 3.4762004390000003, 0.063701782], [3.566, 3.5059797359999996, 0.067333412], [3.134, 3.192, 0.029043202999999997], [3.191, 3.2677446289999996, 0.038280365000000004], [3.191, 3.277671143, 0.03949091], [3.191, 3.307450684, 0.04312254], [3.191, 3.317376953, 0.044333084], [3.191, 3.347156494, 0.047964714], [3.191, 3.3570827640000003, 0.049175259], [3.623, 3.8136999510000003, 0.104860275], [3.566, 3.585391357, 0.077017769], [3.566, 3.5556120609999997, 0.07338613100000001], [3.566, 3.625097168, 0.08185993999999999], [3.566, 3.595317871, 0.07822831], [3.566, 3.664802979, 0.086702118], [3.566, 3.635023682, 0.08307048], [3.566, 3.51590625, 0.068543961], [3.566, 3.704509033, 0.091544289], [3.566, 3.674729492, 0.087912659], [3.566, 3.545685547, 0.072175591], [3.566, 3.714435303, 0.09275483699999999], [3.191, 3.386862305, 0.052806888999999996], [3.191, 3.396788818, 0.054017433], [3.191, 3.4265681150000002, 0.057649067000000005], [3.191, 3.436494629, 0.058859608], [3.191, 3.466273926, 0.062491241], [3.191, 3.4762004390000003, 0.063701782], [3.191, 3.5059797359999996, 0.067333412], [3.191, 3.51590625, 0.068543961], [3.191, 3.545685547, 0.072175591], [3.191, 3.585391357, 0.077017769], [3.191, 3.5556120609999997, 0.07338613100000001], [3.191, 3.595317871, 0.07822831], [3.191, 3.625097168, 0.08185993999999999], [3.191, 3.635023682, 0.08307048], [3.191, 3.664802979, 0.086702118], [3.191, 3.674729492, 0.087912659], [3.191, 3.704509033, 0.091544289], [3.191, 3.714435303, 0.09275483699999999], [3.134, 3.1943000489999998, 0.010182931], [3.134, 3.816, 0.086], [3.134, 3.8136999510000003, 0.104860275], [3.623, 3.192, 0.029043202999999997], [3.191, 3.3196770019999997, 0.025472812999999997], [3.566, 3.3196770019999997, 0.025472812999999997], [3.191, 3.309750488, 0.024262268], [3.566, 3.309750488, 0.024262268], [3.191, 3.399088623, 0.035157162], [3.566, 3.399088623, 0.035157162], [3.191, 3.389162354, 0.033946617000000005], [3.566, 3.389162354, 0.033946617000000005], [3.191, 3.4785004880000003, 0.044841510999999994], [3.566, 3.4785004880000003, 0.044841510999999994], [3.191, 3.468573975, 0.04363097], [3.566, 3.468573975, 0.04363097], [3.191, 3.518206299, 0.049683689], [3.566, 3.518206299, 0.049683689], [3.191, 3.508279785, 0.048473145], [3.566, 3.508279785, 0.048473145], [3.191, 3.5579121089999997, 0.054525864], [3.566, 3.5579121089999997, 0.054525864], [3.191, 3.547985596, 0.053315319], [3.566, 3.547985596, 0.053315319], [3.191, 3.5976179200000002, 0.059368038], [3.566, 3.5976179200000002, 0.059368038], [3.191, 3.587691406, 0.058157494], [3.566, 3.587691406, 0.058157494], [3.191, 3.63732373, 0.064210213], [3.566, 3.63732373, 0.064210213], [3.191, 3.6273972170000004, 0.062999668], [3.566, 3.6273972170000004, 0.062999668], [3.191, 3.677029541, 0.069052391], [3.566, 3.677029541, 0.069052391], [3.191, 3.667103027, 0.067841843], [3.566, 3.667103027, 0.067841843], [3.191, 3.716735352, 0.073894562], [3.566, 3.716735352, 0.073894562], [3.191, 3.7068088379999997, 0.072684021], [3.566, 3.7068088379999997, 0.072684021], [3.191, 3.279971191, 0.020630638], [3.566, 3.279971191, 0.020630638], [3.191, 3.270044678, 0.019420094], [3.566, 3.270044678, 0.019420094], [3.191, 3.3593828119999998, 0.030314986999999998], [3.566, 3.3593828119999998, 0.030314986999999998], [3.191, 3.3494565429999996, 0.029104445], [3.566, 3.3494565429999996, 0.029104445], [3.191, 3.4387946780000003, 0.039999335999999996], [3.566, 3.4387946780000003, 0.039999335999999996], [3.191, 3.428868164, 0.038788795], [3.566, 3.428868164, 0.038788795], [3.623, 3.816, 0.086], [3.623, 3.1943000489999998, 0.010182931], [3.663, 3.192, 0.282], [3.663, 3.173, 0.811], [3.663, 3.192, 0.811], [3.663, 3.173, 0.282], [4.148, 3.173, 0.811], [4.148, 3.192, 0.811], [4.148, 3.173, 0.282], [4.148, 3.192, 0.282], [3.763, 3.173, 0.338], [3.763, 3.173, 0.353], [3.763, 3.173, 0.383], [3.763, 3.173, 0.398], [3.763, 3.173, 0.428], [3.763, 3.173, 0.443], [3.763, 3.173, 0.473], [3.763, 3.173, 0.488], [3.763, 3.173, 0.518], [4.048, 3.173, 0.338], [4.048, 3.173, 0.353], [4.048, 3.173, 0.383], [4.048, 3.173, 0.398], [3.763, 3.173, 0.665], [3.763, 3.173, 0.65], [3.763, 3.173, 0.575], [3.763, 3.173, 0.62], [3.763, 3.173, 0.605], [3.763, 3.173, 0.71], [3.763, 3.173, 0.695], [4.048, 3.173, 0.428], [4.048, 3.173, 0.443], [3.763, 3.173, 0.74], [3.763, 3.173, 0.755], [4.048, 3.173, 0.473], [4.048, 3.173, 0.488], [4.048, 3.173, 0.518], [4.048, 3.173, 0.575], [4.048, 3.173, 0.605], [4.048, 3.173, 0.62], [4.048, 3.173, 0.65], [4.048, 3.173, 0.665], [4.048, 3.173, 0.695], [4.048, 3.173, 0.71], [4.048, 3.173, 0.74], [4.048, 3.173, 0.755], [3.763, 3.192, 0.353], [3.763, 3.192, 0.338], [3.763, 3.192, 0.383], [3.763, 3.192, 0.398], [3.763, 3.192, 0.428], [3.763, 3.192, 0.443], [3.763, 3.192, 0.473], [3.763, 3.192, 0.488], [3.763, 3.192, 0.518], [4.048, 3.192, 0.338], [4.048, 3.192, 0.353], [4.048, 3.192, 0.383], [4.048, 3.192, 0.398], [3.763, 3.192, 0.575], [3.763, 3.192, 0.62], [3.763, 3.192, 0.605], [3.763, 3.192, 0.665], [3.763, 3.192, 0.65], [3.763, 3.192, 0.71], [3.763, 3.192, 0.695], [4.048, 3.192, 0.428], [4.048, 3.192, 0.443], [3.763, 3.192, 0.74], [3.763, 3.192, 0.755], [4.048, 3.192, 0.473], [4.048, 3.192, 0.488], [4.048, 3.192, 0.518], [4.048, 3.192, 0.605], [4.048, 3.192, 0.575], [4.048, 3.192, 0.65], [4.048, 3.192, 0.62], [4.048, 3.192, 0.665], [4.048, 3.192, 0.695], [4.048, 3.192, 0.74], [4.048, 3.192, 0.71], [4.048, 3.192, 0.755], [4.169, 3.173, 0.925], [4.169, 3.716, 0.925], [3.661, 3.173, 0.0], [3.642, 3.293, -0.0], [3.661, 3.293, -0.0], [3.642, 3.626, -0.0], [3.642, 3.293, 0.160060806], [3.642, 3.568048096, 0.475206238], [3.642, 3.293, 0.359939209], [3.642, 3.293, 0.459878387], [3.661, 3.626, -0.0], [3.661, 3.726, -0.0], [3.661, 3.604364258, 0.17741247599999999], [3.661, 3.592258789, 0.276677063], [3.661, 3.580153564, 0.37594164999999996], [3.661, 3.293, 0.160060806], [3.661, 3.568048096, 0.475206238], [3.661, 3.293, 0.26], [3.661, 3.293, 0.359939209], [3.661, 3.293, 0.459878387], [3.661, 3.716, 0.906], [3.661, 3.555942627, 0.574470825], [3.661, 3.5438371579999997, 0.673735413], [3.661, 3.5317316890000003, 0.773], [3.661, 3.293, 0.5598175660000001], [3.661, 3.293, 0.659756775], [3.661, 3.173, 0.906], [3.661, 3.293, 0.773], [3.642, 3.816, 0.086], [3.661, 3.816, 0.086], [3.661, 3.716984863, 0.07392497299999999], [4.15, 3.173, 0.0], [4.169, 3.173, 0.906], [4.169, 3.173, 0.0], [4.15, 3.293, -0.0], [4.169, 3.293, -0.0], [4.169, 3.716, 0.906], [4.15, 3.726, -0.0], [4.15, 3.626, -0.0], [4.15, 3.716984863, 0.07392497299999999], [4.15, 3.604364258, 0.17741247599999999], [4.15, 3.592258789, 0.276677063], [4.15, 3.580153564, 0.37594164999999996], [4.15, 3.293, 0.160060806], [4.15, 3.568048096, 0.475206238], [4.15, 3.293, 0.26], [4.15, 3.293, 0.359939209], [4.15, 3.293, 0.459878387], [4.15, 3.555942627, 0.574470825], [4.15, 3.716, 0.906], [4.15, 3.5438371579999997, 0.673735413], [4.15, 3.5317316890000003, 0.773], [4.15, 3.293, 0.5598175660000001], [4.15, 3.293, 0.659756775], [4.15, 3.293, 0.773], [4.15, 3.173, 0.906], [4.169, 3.626, -0.0], [4.169, 3.726, -0.0], [4.169, 3.716984863, 0.07392497299999999], [4.169, 3.604364258, 0.17741247599999999], [4.169, 3.816, 0.086], [4.169, 3.592258789, 0.276677063], [4.169, 3.580153564, 0.37594164999999996], [4.169, 3.293, 0.160060806], [4.169, 3.568048096, 0.475206238], [4.169, 3.293, 0.26], [4.169, 3.293, 0.359939209], [4.169, 3.293, 0.459878387], [4.169, 3.555942627, 0.574470825], [4.169, 3.5438371579999997, 0.673735413], [4.169, 3.5317316890000003, 0.773], [4.169, 3.293, 0.5598175660000001], [4.169, 3.293, 0.659756775], [4.169, 3.293, 0.773], [4.093, 3.277671143, 0.03949091], [4.093, 3.2677446289999996, 0.038280365000000004], [4.093, 3.307450684, 0.04312254], [4.093, 3.317376953, 0.044333084], [4.093, 3.347156494, 0.047964714], [4.093, 3.3570827640000003, 0.049175259], [4.093, 3.386862305, 0.052806888999999996], [4.093, 3.396788818, 0.054017433], [4.093, 3.4265681150000002, 0.057649067000000005], [4.093, 3.436494629, 0.058859608], [4.093, 3.466273926, 0.062491241], [4.093, 3.4762004390000003, 0.063701782], [4.093, 3.5059797359999996, 0.067333412], [3.661, 3.192, 0.029043202999999997], [3.718, 3.2677446289999996, 0.038280365000000004], [3.718, 3.277671143, 0.03949091], [3.718, 3.307450684, 0.04312254], [3.718, 3.317376953, 0.044333084], [3.718, 3.347156494, 0.047964714], [3.718, 3.3570827640000003, 0.049175259], [4.093, 3.585391357, 0.077017769], [4.093, 3.5556120609999997, 0.07338613100000001], [4.093, 3.625097168, 0.08185993999999999], [4.093, 3.595317871, 0.07822831], [4.093, 3.664802979, 0.086702118], [4.093, 3.635023682, 0.08307048], [4.093, 3.51590625, 0.068543961], [4.093, 3.704509033, 0.091544289], [4.093, 3.674729492, 0.087912659], [4.093, 3.545685547, 0.072175591], [4.093, 3.714435303, 0.09275483699999999], [3.718, 3.386862305, 0.052806888999999996], [3.718, 3.396788818, 0.054017433], [3.718, 3.4265681150000002, 0.057649067000000005], [3.718, 3.436494629, 0.058859608], [3.718, 3.466273926, 0.062491241], [3.718, 3.4762004390000003, 0.063701782], [3.718, 3.5059797359999996, 0.067333412], [3.718, 3.51590625, 0.068543961], [3.718, 3.545685547, 0.072175591], [3.718, 3.585391357, 0.077017769], [3.718, 3.5556120609999997, 0.07338613100000001], [3.718, 3.595317871, 0.07822831], [3.718, 3.625097168, 0.08185993999999999], [3.718, 3.635023682, 0.08307048], [3.718, 3.664802979, 0.086702118], [3.718, 3.674729492, 0.087912659], [3.718, 3.704509033, 0.091544289], [3.718, 3.714435303, 0.09275483699999999], [3.661, 3.8136999510000003, 0.104860275], [3.661, 3.1943000489999998, 0.010182931], [4.15, 3.1943000489999998, 0.010182931], [4.15, 3.192, 0.029043202999999997], [4.15, 3.8136999510000003, 0.104860275], [4.15, 3.816, 0.086], [3.718, 3.3196770019999997, 0.025472812999999997], [4.093, 3.3196770019999997, 0.025472812999999997], [3.718, 3.309750488, 0.024262268], [4.093, 3.309750488, 0.024262268], [3.718, 3.399088623, 0.035157162], [4.093, 3.399088623, 0.035157162], [3.718, 3.389162354, 0.033946617000000005], [4.093, 3.389162354, 0.033946617000000005], [3.718, 3.4785004880000003, 0.044841510999999994], [4.093, 3.4785004880000003, 0.044841510999999994], [3.718, 3.468573975, 0.04363097], [4.093, 3.468573975, 0.04363097], [3.718, 3.518206299, 0.049683689], [4.093, 3.518206299, 0.049683689], [3.718, 3.508279785, 0.048473145], [4.093, 3.508279785, 0.048473145], [3.718, 3.5579121089999997, 0.054525864], [4.093, 3.5579121089999997, 0.054525864], [3.718, 3.547985596, 0.053315319], [4.093, 3.547985596, 0.053315319], [3.718, 3.5976179200000002, 0.059368038], [4.093, 3.5976179200000002, 0.059368038], [3.718, 3.587691406, 0.058157494], [4.093, 3.587691406, 0.058157494], [3.718, 3.63732373, 0.064210213], [4.093, 3.63732373, 0.064210213], [3.718, 3.6273972170000004, 0.062999668], [4.093, 3.6273972170000004, 0.062999668], [3.718, 3.677029541, 0.069052391], [4.093, 3.677029541, 0.069052391], [3.718, 3.667103027, 0.067841843], [4.093, 3.667103027, 0.067841843], [3.718, 3.716735352, 0.073894562], [4.093, 3.716735352, 0.073894562], [3.718, 3.7068088379999997, 0.072684021], [4.093, 3.7068088379999997, 0.072684021], [3.718, 3.279971191, 0.020630638], [4.093, 3.279971191, 0.020630638], [3.718, 3.270044678, 0.019420094], [4.093, 3.270044678, 0.019420094], [3.718, 3.3593828119999998, 0.030314986999999998], [4.093, 3.3593828119999998, 0.030314986999999998], [3.718, 3.3494565429999996, 0.029104445], [4.093, 3.3494565429999996, 0.029104445], [3.718, 3.4387946780000003, 0.039999335999999996], [4.093, 3.4387946780000003, 0.039999335999999996], [3.718, 3.428868164, 0.038788795], [4.093, 3.428868164, 0.038788795], [4.343381836, 3.2063579100000004, 0.282], [4.346681152, 3.187646729, 0.811], [4.343381836, 3.2063579100000004, 0.811], [4.346681152, 3.187646729, 0.282], [4.8243125, 3.271865967, 0.811], [4.821013184000001, 3.290577393, 0.811], [4.8243125, 3.271865967, 0.282], [4.821013184000001, 3.290577393, 0.282], [4.4451616210000005, 3.2050114749999996, 0.338], [4.4451616210000005, 3.2050114749999996, 0.353], [4.4451616210000005, 3.2050114749999996, 0.383], [4.4451616210000005, 3.2050114749999996, 0.398], [4.4451616210000005, 3.2050114749999996, 0.428], [4.4451616210000005, 3.2050114749999996, 0.443], [4.4451616210000005, 3.2050114749999996, 0.473], [4.4451616210000005, 3.2050114749999996, 0.488], [4.4451616210000005, 3.2050114749999996, 0.518], [4.7258320309999995, 3.254501221, 0.338], [4.7258320309999995, 3.254501221, 0.353], [4.7258320309999995, 3.254501221, 0.383], [4.7258320309999995, 3.254501221, 0.398], [4.4451616210000005, 3.2050114749999996, 0.665], [4.4451616210000005, 3.2050114749999996, 0.65], [4.4451616210000005, 3.2050114749999996, 0.575], [4.4451616210000005, 3.2050114749999996, 0.62], [4.4451616210000005, 3.2050114749999996, 0.605], [4.4451616210000005, 3.2050114749999996, 0.71], [4.4451616210000005, 3.2050114749999996, 0.695], [4.7258320309999995, 3.254501221, 0.428], [4.7258320309999995, 3.254501221, 0.443], [4.4451616210000005, 3.2050114749999996, 0.74], [4.4451616210000005, 3.2050114749999996, 0.755], [4.7258320309999995, 3.254501221, 0.473], [4.7258320309999995, 3.254501221, 0.488], [4.7258320309999995, 3.254501221, 0.518], [4.7258320309999995, 3.254501221, 0.575], [4.7258320309999995, 3.254501221, 0.605], [4.7258320309999995, 3.254501221, 0.62], [4.7258320309999995, 3.254501221, 0.65], [4.7258320309999995, 3.254501221, 0.665], [4.7258320309999995, 3.254501221, 0.695], [4.7258320309999995, 3.254501221, 0.71], [4.7258320309999995, 3.254501221, 0.74], [4.7258320309999995, 3.254501221, 0.755], [4.441862305, 3.223722656, 0.353], [4.441862305, 3.223722656, 0.338], [4.441862305, 3.223722656, 0.383], [4.441862305, 3.223722656, 0.398], [4.441862305, 3.223722656, 0.428], [4.441862305, 3.223722656, 0.443], [4.441862305, 3.223722656, 0.473], [4.441862305, 3.223722656, 0.488], [4.441862305, 3.223722656, 0.518], [4.722532715000001, 3.273212402, 0.338], [4.722532715000001, 3.273212402, 0.353], [4.722532715000001, 3.273212402, 0.383], [4.722532715000001, 3.273212402, 0.398], [4.441862305, 3.223722656, 0.575], [4.441862305, 3.223722656, 0.62], [4.441862305, 3.223722656, 0.605], [4.441862305, 3.223722656, 0.665], [4.441862305, 3.223722656, 0.65], [4.441862305, 3.223722656, 0.71], [4.441862305, 3.223722656, 0.695], [4.722532715000001, 3.273212402, 0.428], [4.722532715000001, 3.273212402, 0.443], [4.441862305, 3.223722656, 0.74], [4.441862305, 3.223722656, 0.755], [4.722532715000001, 3.273212402, 0.473], [4.722532715000001, 3.273212402, 0.488], [4.722532715000001, 3.273212402, 0.518], [4.722532715000001, 3.273212402, 0.605], [4.722532715000001, 3.273212402, 0.575], [4.722532715000001, 3.273212402, 0.65], [4.722532715000001, 3.273212402, 0.62], [4.722532715000001, 3.273212402, 0.665], [4.722532715000001, 3.273212402, 0.695], [4.722532715000001, 3.273212402, 0.74], [4.722532715000001, 3.273212402, 0.71], [4.722532715000001, 3.273212402, 0.755], [4.326, 3.184, 0.925], [4.231708984, 3.718750488, 0.925], [4.326, 3.184, 0.906], [4.844993652, 3.2755126949999998, 0.906], [4.844993652, 3.2755126949999998, 0.925], [4.750702637000001, 3.810263184, 0.925], [4.326, 3.184, 0.0], [4.344711426, 3.1872993160000003, 0.906], [4.344711426, 3.1872993160000003, 0.0], [4.305162109, 3.302177002, -0.0], [4.323873535, 3.305476318, -0.0], [4.250420409999999, 3.722049805, 0.906], [4.247337402, 3.63011792, -0.0], [4.231538086, 3.719720459, 0.07392497299999999], [4.251094237999999, 3.608811035, 0.17741247599999999], [4.214344238, 3.817231445, 0.086], [4.253196289000001, 3.596889404, 0.276677063], [4.25529834, 3.584967773, 0.37594164999999996], [4.305162109, 3.302177002, 0.160060806], [4.257400391, 3.5730463869999998, 0.475206238], [4.305162109, 3.302177002, 0.26], [4.305162109, 3.302177002, 0.359939209], [4.305162109, 3.302177002, 0.459878387], [4.25950293, 3.5611247560000003, 0.574470825], [4.231708984, 3.718750488, 0.906], [4.26160498, 3.5492033689999998, 0.673735413], [4.263707031, 3.5372817380000003, 0.773], [4.305162109, 3.302177002, 0.5598175660000001], [4.305162109, 3.302177002, 0.659756775], [4.305162109, 3.302177002, 0.773], [4.266048828, 3.6334172359999997, -0.0], [4.269805664000001, 3.612110352, 0.17741247599999999], [4.233055664, 3.8205307619999997, 0.086], [4.271907715, 3.600188721, 0.276677063], [4.274009766, 3.58826709, 0.37594164999999996], [4.323873535, 3.305476318, 0.160060806], [4.276111815999999, 3.576345703, 0.475206238], [4.323873535, 3.305476318, 0.26], [4.323873535, 3.305476318, 0.359939209], [4.323873535, 3.305476318, 0.459878387], [4.278213867000001, 3.564424072, 0.574470825], [4.280315918, 3.552502686, 0.673735413], [4.2824184569999995, 3.540581055, 0.773], [4.323873535, 3.305476318, 0.5598175660000001], [4.323873535, 3.305476318, 0.659756775], [4.323873535, 3.305476318, 0.773], [4.250249512, 3.723019775, 0.07392497299999999], [4.229972656, 3.7285986330000003, -0.0], [4.248684082, 3.731897949, -0.0], [4.826282227, 3.2722133789999996, 0.0], [4.844993652, 3.2755126949999998, 0.0], [4.805444336, 3.390390137, -0.0], [4.824155762, 3.393689453, -0.0], [4.750702637000001, 3.810263184, 0.906], [4.730254883, 3.8168120119999998, -0.0], [4.747619629, 3.718331299, -0.0], [4.731820312, 3.807933594, 0.07392497299999999], [4.751376465, 3.69702417, 0.17741247599999999], [4.714626465, 3.9054445799999997, 0.086], [4.753479004, 3.6851027829999996, 0.276677063], [4.7555810549999995, 3.673181152, 0.37594164999999996], [4.805444336, 3.390390137, 0.160060806], [4.757683105, 3.661259521, 0.475206238], [4.805444336, 3.390390137, 0.26], [4.805444336, 3.390390137, 0.359939209], [4.805444336, 3.390390137, 0.459878387], [4.7597851559999995, 3.649338135, 0.574470825], [4.7319912109999995, 3.806963867, 0.906], [4.761887207, 3.637416504, 0.673735413], [4.763989258, 3.625495117, 0.773], [4.805444336, 3.390390137, 0.5598175660000001], [4.805444336, 3.390390137, 0.659756775], [4.805444336, 3.390390137, 0.773], [4.826282227, 3.2722133789999996, 0.906], [4.766331054999999, 3.721630615, -0.0], [4.750531737999999, 3.81123291, 0.07392497299999999], [4.770087891, 3.700323486, 0.17741247599999999], [4.733337891, 3.908743896, 0.086], [4.772189941, 3.6884021, 0.276677063], [4.774291992, 3.676480469, 0.37594164999999996], [4.824155762, 3.393689453, 0.160060806], [4.776394531, 3.664558838, 0.475206238], [4.824155762, 3.393689453, 0.26], [4.824155762, 3.393689453, 0.359939209], [4.824155762, 3.393689453, 0.459878387], [4.778496582, 3.652637451, 0.574470825], [4.780598632999999, 3.64071582, 0.673735413], [4.782700684, 3.628794434, 0.773], [4.824155762, 3.393689453, 0.5598175660000001], [4.824155762, 3.393689453, 0.659756775], [4.824155762, 3.393689453, 0.773], [4.748966309, 3.820111328, -0.0], [4.751972168, 3.36539624, 0.03949091], [4.7536962890000005, 3.355620605, 0.038280365000000004], [4.74680127, 3.394723389, 0.04312254], [4.7450776370000005, 3.404499023, 0.044333084], [4.73990625, 3.433825928, 0.047964714], [4.7381826170000005, 3.443601562, 0.049175259], [4.733011719, 3.472928467, 0.052806888999999996], [4.731287598, 3.482704102, 0.054017433], [4.726116699, 3.51203125, 0.057649067000000005], [4.724393065999999, 3.521806885, 0.058859608], [4.7192216799999995, 3.551133789, 0.062491241], [4.717498047, 3.560909424, 0.063701782], [4.712327148, 3.590236328, 0.067333412], [4.341412109, 3.206010742, 0.029043202999999997], [4.3843930659999995, 3.290502686, 0.038280365000000004], [4.382669434, 3.30027832, 0.03949091], [4.377498535, 3.329605225, 0.04312254], [4.375774414, 3.339380859, 0.044333084], [4.370603516, 3.3687077640000003, 0.047964714], [4.368879883, 3.3784833979999997, 0.049175259], [4.715025879, 3.9031796880000003, 0.104860275], [4.698537109, 3.66844165, 0.077017769], [4.703708496, 3.639114746, 0.07338613100000001], [4.691642578, 3.707544189, 0.08185993999999999], [4.696813477, 3.678217285, 0.07822831], [4.684747559000001, 3.746646729, 0.086702118], [4.6899189450000005, 3.717319824, 0.08307048], [4.710603027, 3.600011963, 0.068543961], [4.677852539000001, 3.7857495119999998, 0.091544289], [4.683023926, 3.756422363, 0.087912659], [4.705432129, 3.629338867, 0.072175591], [4.676128906, 3.795525146, 0.09275483699999999], [4.363708496, 3.407810547, 0.052806888999999996], [4.361984863, 3.417586182, 0.054017433], [4.356813965000001, 3.446913086, 0.057649067000000005], [4.355089844, 3.456688721, 0.058859608], [4.349918945000001, 3.486015625, 0.062491241], [4.348195312, 3.49579126, 0.063701782], [4.343023926, 3.525118408, 0.067333412], [4.341300293000001, 3.534894043, 0.068543961], [4.3361293949999995, 3.564220947, 0.072175591], [4.329234375, 3.603323486, 0.077017769], [4.334405273000001, 3.5739965820000004, 0.07338613100000001], [4.327510742, 3.6130991210000003, 0.07822831], [4.322339355, 3.6424260250000002, 0.08185993999999999], [4.3206157229999995, 3.652201904, 0.08307048], [4.315444824, 3.681528809, 0.086702118], [4.313721191, 3.691304443, 0.087912659], [4.308549804999999, 3.720631348, 0.091544289], [4.306826172, 3.730406982, 0.09275483699999999], [4.2334550779999995, 3.818265625, 0.104860275], [4.822583496, 3.293189697, 0.010182931], [4.341012695000001, 3.208275635, 0.010182931], [4.8229829099999995, 3.2909245609999997, 0.029043202999999997], [4.375375, 3.341645996, 0.025472812999999997], [4.744678223, 3.406763916, 0.025472812999999997], [4.3770991210000005, 3.3318703609999996, 0.024262268], [4.746401855, 3.396988281, 0.024262268], [4.361585449000001, 3.419851318, 0.035157162], [4.730888184, 3.484969238, 0.035157162], [4.363309082, 3.4100754390000003, 0.033946617000000005], [4.732612305, 3.475193604, 0.033946617000000005], [4.347795898, 3.498056396, 0.044841510999999994], [4.717098633, 3.563174561, 0.044841510999999994], [4.349519531, 3.488280762, 0.04363097], [4.718822266, 3.553398926, 0.04363097], [4.340900878999999, 3.537158936, 0.049683689], [4.710204102, 3.6022771, 0.049683689], [4.342624512, 3.527383301, 0.048473145], [4.711927734, 3.592501465, 0.048473145], [4.334006348, 3.576261719, 0.054525864], [4.703309082, 3.641379639, 0.054525864], [4.33572998, 3.566486084, 0.053315319], [4.705032715000001, 3.6316040039999997, 0.053315319], [4.327111328, 3.615364258, 0.059368038], [4.696414062, 3.680482422, 0.059368038], [4.328834960999999, 3.605588623, 0.058157494], [4.698137695000001, 3.670706787, 0.058157494], [4.320216309, 3.654466797, 0.064210213], [4.689519531, 3.719584961, 0.064210213], [4.321939940999999, 3.644691162, 0.062999668], [4.691243164, 3.7098093260000002, 0.062999668], [4.3133217770000005, 3.6935695799999997, 0.069052391], [4.682624512, 3.7586875, 0.069052391], [4.31504541, 3.6837939449999997, 0.067841843], [4.6843481449999995, 3.748911865, 0.067841843], [4.306426758, 3.7326721189999996, 0.073894562], [4.675729492, 3.797790283, 0.073894562], [4.308150391, 3.722896484, 0.072684021], [4.677453125, 3.788014404, 0.072684021], [4.38227002, 3.302543457, 0.020630638], [4.751572754, 3.367661377, 0.020630638], [4.383993652, 3.2927678219999996, 0.019420094], [4.753296875, 3.357885742, 0.019420094], [4.368480469, 3.3807485350000004, 0.030314986999999998], [4.737783203, 3.445866699, 0.030314986999999998], [4.370204102, 3.3709729000000004, 0.029104445], [4.7395068359999994, 3.436091064, 0.029104445], [4.35469043, 3.458953857, 0.039999335999999996], [4.723993652, 3.524071777, 0.039999335999999996], [4.356414550999999, 3.449178223, 0.038788795], [4.725717285, 3.514296143, 0.038788795], [1.932980225, 3.292064697, 0.282], [1.9296809080000001, 3.273353271, 0.811], [1.932980225, 3.292064697, 0.811], [1.9296809080000001, 3.273353271, 0.282], [2.407312744, 3.1891340329999998, 0.811], [2.407312744, 3.1891340329999998, 0.282], [2.4106120609999997, 3.207845459, 0.282], [2.028161743, 3.2559885250000002, 0.338], [2.028161743, 3.2559885250000002, 0.353], [2.028161743, 3.2559885250000002, 0.383], [2.028161743, 3.2559885250000002, 0.398], [2.028161743, 3.2559885250000002, 0.428], [2.028161743, 3.2559885250000002, 0.443], [2.028161743, 3.2559885250000002, 0.473], [2.028161743, 3.2559885250000002, 0.488], [2.028161743, 3.2559885250000002, 0.518], [2.308832031, 3.206498779, 0.338], [2.308832031, 3.206498779, 0.353], [2.308832031, 3.206498779, 0.383], [2.308832031, 3.206498779, 0.398], [2.028161743, 3.2559885250000002, 0.665], [2.028161743, 3.2559885250000002, 0.65], [2.028161743, 3.2559885250000002, 0.575], [2.028161743, 3.2559885250000002, 0.62], [2.028161743, 3.2559885250000002, 0.605], [2.028161743, 3.2559885250000002, 0.71], [2.028161743, 3.2559885250000002, 0.695], [2.308832031, 3.206498779, 0.428], [2.308832031, 3.206498779, 0.443], [2.028161743, 3.2559885250000002, 0.74], [2.028161743, 3.2559885250000002, 0.755], [2.308832031, 3.206498779, 0.473], [2.308832031, 3.206498779, 0.488], [2.308832031, 3.206498779, 0.518], [2.308832031, 3.206498779, 0.575], [2.308832031, 3.206498779, 0.605], [2.308832031, 3.206498779, 0.62], [2.308832031, 3.206498779, 0.65], [2.308832031, 3.206498779, 0.665], [2.308832031, 3.206498779, 0.695], [2.308832031, 3.206498779, 0.71], [2.308832031, 3.206498779, 0.74], [2.308832031, 3.206498779, 0.755], [2.0314610600000003, 3.274699951, 0.353], [2.0314610600000003, 3.274699951, 0.338], [2.0314610600000003, 3.274699951, 0.383], [2.0314610600000003, 3.274699951, 0.398], [2.0314610600000003, 3.274699951, 0.428], [2.0314610600000003, 3.274699951, 0.443], [2.0314610600000003, 3.274699951, 0.473], [2.0314610600000003, 3.274699951, 0.488], [2.0314610600000003, 3.274699951, 0.518], [2.312131348, 3.2252102049999998, 0.338], [2.312131348, 3.2252102049999998, 0.353], [2.312131348, 3.2252102049999998, 0.383], [2.312131348, 3.2252102049999998, 0.398], [2.0314610600000003, 3.274699951, 0.575], [2.0314610600000003, 3.274699951, 0.62], [2.0314610600000003, 3.274699951, 0.605], [2.0314610600000003, 3.274699951, 0.665], [2.0314610600000003, 3.274699951, 0.65], [2.0314610600000003, 3.274699951, 0.71], [2.0314610600000003, 3.274699951, 0.695], [2.312131348, 3.2252102049999998, 0.428], [2.312131348, 3.2252102049999998, 0.443], [2.0314610600000003, 3.274699951, 0.74], [2.0314610600000003, 3.274699951, 0.755], [2.312131348, 3.2252102049999998, 0.473], [2.312131348, 3.2252102049999998, 0.488], [2.312131348, 3.2252102049999998, 0.518], [2.312131348, 3.2252102049999998, 0.605], [2.312131348, 3.2252102049999998, 0.575], [2.312131348, 3.2252102049999998, 0.65], [2.312131348, 3.2252102049999998, 0.62], [2.312131348, 3.2252102049999998, 0.665], [2.312131348, 3.2252102049999998, 0.695], [2.312131348, 3.2252102049999998, 0.74], [2.312131348, 3.2252102049999998, 0.71], [2.312131348, 3.2252102049999998, 0.755], [2.4106120609999997, 3.207845459, 0.811], [1.909, 3.277, 0.925], [2.003291016, 3.811750488, 0.925], [1.909, 3.277, 0.906], [2.427993652, 3.185487305, 0.925], [2.5222846679999997, 3.720238037, 0.925], [1.909, 3.277, 0.0], [1.927711304, 3.273700684, 0.0], [1.929837769, 3.395177002, -0.0], [1.948549072, 3.391877686, -0.0], [1.9876625979999998, 3.72311792, -0.0], [2.003461914, 3.8127204590000003, 0.07392497299999999], [1.98390564, 3.701811035, 0.17741247599999999], [1.981803589, 3.689889404, 0.276677063], [1.9797014160000002, 3.6779677729999998, 0.37594164999999996], [1.929837769, 3.395177002, 0.160060806], [1.977599365, 3.6660463869999997, 0.475206238], [1.929837769, 3.395177002, 0.26], [1.929837769, 3.395177002, 0.359939209], [1.929837769, 3.395177002, 0.459878387], [1.975497314, 3.6541247560000003, 0.574470825], [2.003291016, 3.811750488, 0.906], [1.973395142, 3.6422033689999997, 0.673735413], [1.971293091, 3.6302817380000003, 0.773], [1.929837769, 3.395177002, 0.5598175660000001], [1.929837769, 3.395177002, 0.659756775], [1.929837769, 3.395177002, 0.773], [2.0063740230000002, 3.719818604, -0.0], [2.02373877, 3.818299316, -0.0], [2.002616943, 3.698511719, 0.17741247599999999], [2.000514893, 3.686590088, 0.276677063], [1.998412842, 3.674668457, 0.37594164999999996], [1.948549072, 3.391877686, 0.160060806], [1.9963106689999999, 3.66274707, 0.475206238], [1.948549072, 3.391877686, 0.26], [1.948549072, 3.391877686, 0.359939209], [1.948549072, 3.391877686, 0.459878387], [2.022002319, 3.808451416, 0.906], [1.9942086179999998, 3.650825439, 0.574470825], [1.992106567, 3.638904053, 0.673735413], [1.990004395, 3.626982422, 0.773], [1.948549072, 3.391877686, 0.5598175660000001], [1.948549072, 3.391877686, 0.659756775], [1.927711304, 3.273700684, 0.906], [1.948549072, 3.391877686, 0.773], [2.020655762, 3.910231445, 0.086], [2.039367065, 3.906932129, 0.086], [2.02217334, 3.8094211430000002, 0.07392497299999999], [2.005027466, 3.8215986330000002, -0.0], [2.4092822270000003, 3.188786621, 0.0], [2.427993652, 3.185487305, 0.0], [2.430120117, 3.306963623, -0.0], [2.448831543, 3.303664307, -0.0], [2.503573242, 3.723537354, 0.906], [2.5222846679999997, 3.720238037, 0.906], [2.487945068, 3.634904541, -0.0], [2.503744385, 3.72450708, 0.07392497299999999], [2.484187988, 3.613597656, 0.17741247599999999], [2.482085938, 3.601676025, 0.276677063], [2.479983887, 3.589754639, 0.37594164999999996], [2.430120117, 3.306963623, 0.160060806], [2.477881592, 3.5778330080000003, 0.475206238], [2.430120117, 3.306963623, 0.26], [2.430120117, 3.306963623, 0.359939209], [2.430120117, 3.306963623, 0.459878387], [2.475779541, 3.565911621, 0.574470825], [2.47367749, 3.55398999, 0.673735413], [2.471575439, 3.5420686040000002, 0.773], [2.430120117, 3.306963623, 0.5598175660000001], [2.430120117, 3.306963623, 0.659756775], [2.430120117, 3.306963623, 0.773], [2.4092822270000003, 3.188786621, 0.906], [2.50665625, 3.631605225, -0.0], [2.52402124, 3.730086182, -0.0], [2.522455566, 3.7212077640000003, 0.07392497299999999], [2.502899414, 3.61029834, 0.17741247599999999], [2.539649414, 3.81871875, 0.086], [2.500797119, 3.598376709, 0.276677063], [2.498695068, 3.586455322, 0.37594164999999996], [2.448831543, 3.303664307, 0.160060806], [2.496593018, 3.574533691, 0.475206238], [2.448831543, 3.303664307, 0.26], [2.448831543, 3.303664307, 0.359939209], [2.448831543, 3.303664307, 0.459878387], [2.4944909670000004, 3.562612305, 0.574470825], [2.492388916, 3.550690674, 0.673735413], [2.490286865, 3.538769287, 0.773], [2.448831543, 3.303664307, 0.5598175660000001], [2.448831543, 3.303664307, 0.659756775], [2.427993652, 3.185487305, 0.906], [2.448831543, 3.303664307, 0.773], [2.5053098140000003, 3.733385498, -0.0], [2.371324219, 3.301765625, 0.03949091], [2.3696005860000002, 3.2919899900000003, 0.038280365000000004], [2.376495361, 3.331092529, 0.04312254], [2.378218994, 3.340868408, 0.044333084], [2.3833901369999997, 3.370195312, 0.047964714], [2.385114014, 3.379970947, 0.049175259], [2.390285156, 3.4092978520000004, 0.052806888999999996], [2.392008789, 3.419073486, 0.054017433], [2.397179932, 3.4484003910000003, 0.057649067000000005], [2.3989035640000003, 3.458176025, 0.058859608], [2.4040747070000004, 3.487503174, 0.062491241], [2.4057985840000002, 3.497278809, 0.063701782], [2.4109697270000003, 3.526605713, 0.067333412], [2.000297607, 3.357108154, 0.038280365000000004], [2.002021362, 3.366883789, 0.03949091], [2.007192505, 3.396210693, 0.04312254], [2.008916138, 3.405986328, 0.044333084], [2.01408728, 3.435313232, 0.047964714], [2.015811035, 3.445088867, 0.049175259], [2.424759277, 3.604811035, 0.077017769], [2.419588135, 3.575483887, 0.07338613100000001], [2.4316540530000004, 3.643913574, 0.08185993999999999], [2.4264829100000003, 3.61458667, 0.07822831], [2.438549072, 3.6830161130000003, 0.086702118], [2.4333779300000002, 3.653689209, 0.08307048], [2.412693359, 3.536381348, 0.068543961], [2.445443848, 3.722118896, 0.091544289], [2.440272705, 3.6927917480000003, 0.087912659], [2.417864502, 3.565708252, 0.072175591], [2.44716748, 3.731894531, 0.09275483699999999], [2.020982178, 3.474416016, 0.052806888999999996], [2.022705933, 3.48419165, 0.054017433], [2.027877075, 3.513518555, 0.057649067000000005], [2.029600708, 3.523294189, 0.058859608], [2.034771851, 3.552621094, 0.062491241], [2.0364956050000003, 3.562396729, 0.063701782], [2.041666748, 3.5917238769999997, 0.067333412], [2.043390381, 3.6014995119999997, 0.068543961], [2.048561523, 3.6308264159999997, 0.072175591], [2.055456299, 3.669928955, 0.077017769], [2.0502851559999997, 3.6406020509999997, 0.07338613100000001], [2.0571801759999997, 3.6797045899999996, 0.07822831], [2.062351318, 3.709031494, 0.08185993999999999], [2.0640749510000003, 3.718807373, 0.08307048], [2.069246094, 3.748134277, 0.086702118], [2.070969727, 3.757909912, 0.087912659], [2.076140869, 3.787236816, 0.091544289], [2.0778647460000004, 3.797012451, 0.09275483699999999], [1.931410034, 3.294677002, 0.010182931], [1.9310106200000001, 3.292412109, 0.029043202999999997], [2.520938232, 3.822018066, 0.086], [2.412581543, 3.207498047, 0.029043202999999997], [2.038967773, 3.904666992, 0.104860275], [2.520538818, 3.81975293, 0.104860275], [2.009315552, 3.4082514649999998, 0.025472812999999997], [2.378618408, 3.343133301, 0.025472812999999997], [2.007591919, 3.3984758299999998, 0.024262268], [2.3768947750000002, 3.333357666, 0.024262268], [2.023105225, 3.4864567870000003, 0.035157162], [2.392408203, 3.421338623, 0.035157162], [2.021381592, 3.4766809079999996, 0.033946617000000005], [2.39068457, 3.411562988, 0.033946617000000005], [2.03689502, 3.564661865, 0.044841510999999994], [2.406197998, 3.499543945, 0.044841510999999994], [2.035171265, 3.55488623, 0.04363097], [2.4044741210000002, 3.489768066, 0.04363097], [2.043789795, 3.603764404, 0.049683689], [2.413092773, 3.538646484, 0.049683689], [2.0420661620000002, 3.5939887699999997, 0.048473145], [2.411369141, 3.5288708499999997, 0.048473145], [2.05068457, 3.6428671880000003, 0.054525864], [2.419987549, 3.577749023, 0.054525864], [2.048960938, 3.6330915530000003, 0.053315319], [2.418263916, 3.567973389, 0.053315319], [2.05757959, 3.6819697270000002, 0.059368038], [2.426882324, 3.616851562, 0.059368038], [2.055855713, 3.6721940920000002, 0.058157494], [2.425158691, 3.607075928, 0.058157494], [2.064474365, 3.721072266, 0.064210213], [2.433777344, 3.655954346, 0.064210213], [2.062750732, 3.711296631, 0.062999668], [2.4320534670000002, 3.646178711, 0.062999668], [2.071369141, 3.760175049, 0.069052391], [2.440672119, 3.695056885, 0.069052391], [2.0696455080000002, 3.750399414, 0.067841843], [2.4389484859999997, 3.68528125, 0.067841843], [2.0782641600000002, 3.799277588, 0.073894562], [2.447566895, 3.734159424, 0.073894562], [2.076540283, 3.789501953, 0.072684021], [2.445843262, 3.724383789, 0.072684021], [2.002420776, 3.369148926, 0.020630638], [2.371723633, 3.304030762, 0.020630638], [2.000697021, 3.359373291, 0.019420094], [2.37, 3.294255127, 0.019420094], [2.016210449, 3.4473540039999997, 0.030314986999999998], [2.3855134280000003, 3.382236084, 0.030314986999999998], [2.014486694, 3.4375783689999997, 0.029104445], [2.383789551, 3.372460449, 0.029104445], [2.030000122, 3.525559326, 0.039999335999999996], [2.3993029790000002, 3.460441162, 0.039999335999999996], [2.028276367, 3.515783691, 0.038788795], [2.397579346, 3.450665527, 0.038788795], [2.4129809570000003, 3.209763184, 0.010182931], [1.98, 3.875, 0.925], [1.803, 3.088, 0.95], [1.98, 3.875, 0.95], [1.803, 3.088, 0.925], [2.454, 2.973, 0.925], [2.454, 2.973, 0.95], [4.262, 2.973, 0.925], [4.262, 2.973, 0.95], [4.912, 3.088, 0.95], [4.912, 3.088, 0.925], [4.776, 3.875, 0.925], [4.776, 3.875, 0.95], [4.208, 3.774846191, 0.925], [4.208, 3.774846191, 0.95], [2.548, 3.774846191, 0.95], [2.548, 3.774846191, 0.925]], "color": [25, 25, 25], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Raised Floor": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [9, 8, 6], [7, 9, 6], [11, 10, 9], [9, 10, 8], [2, 0, 10], [11, 2, 10], [1, 10, 0], [1, 4, 10], [4, 8, 10], [4, 6, 8], [3, 2, 11], [3, 11, 5], [5, 11, 9], [5, 9, 7]], "pts": [[1.8688900149999998, 3.281033203, 0.0], [1.385890015, 0.65, 0.0], [1.8688900149999998, 3.281033203, 0.268], [1.385890015, 0.65, 0.268], [5.389890137, 0.65, 0.0], [5.389890137, 0.65, 0.268], [4.906890137, 3.281033203, 0.0], [4.906890137, 3.281033203, 0.268], [4.275890137, 3.157388672, 0.0], [4.275890137, 3.157388672, 0.268], [2.501889893, 3.157388672, 0.0], [2.501889893, 3.157388672, 0.268]], "color": [25, 25, 25], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Back": {"tris": [[3, 5, 6], [4, 3, 6], [4, 6, 7], [1, 4, 7], [2, 1, 7], [2, 7, 8], [0, 2, 8], [0, 8, 9]], "pts": [[-0.0, 1.476, 0.0], [3.387889893, 0.0, 0.0], [0.701876343, 0.473616669, 0.0], [6.775779785, 1.476, 0.0], [6.073903809, 0.473616669, 0.0], [6.775779785, 1.476, 4.435], [6.073903809, 0.473616669, 4.1090214839999994], [3.387889893, -0.0, 3.955], [0.701876343, 0.473616669, 4.1090214839999994], [-0.0, 1.476, 4.435]], "color": [100, 100, 100], "sides": [1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Front": {"tris": [[0, 1, 2], [0, 2, 3], [5, 0, 3], [5, 3, 6], [2, 7, 6], [2, 6, 3], [4, 7, 2], [1, 4, 2]], "pts": [[4.857242188, 7.601, 0.0], [1.9185378419999999, 7.601, 0.0], [1.9185378419999999, 7.601, 2.0], [4.857242188, 7.601, 2.0], [0.9849625240000001, 7.062, 0.0], [5.790817382999999, 7.062, 0.0], [5.790817382999999, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45]], "color": [100, 100, 100], "sides": [1, 1, 1, 1, 1, 1, 1, 1]}, "Walls Side": {"tris": [[2, 3, 4], [2, 4, 6], [5, 0, 7], [1, 0, 5]], "pts": [[-0.0, 1.476, 0.0], [0.9849625240000001, 7.062, 0.0], [6.775779785, 1.476, 0.0], [5.790817382999999, 7.062, 0.0], [5.790817382999999, 7.062, 3.45], [0.9849625240000001, 7.062, 3.45], [6.775779785, 1.476, 4.435], [-0.0, 1.476, 4.435]], "color": [180, 180, 180], "sides": [1, 1, 1, 1]}, "Windows": {"tris": [[2, 1, 0], [3, 1, 2], [3, 4, 1], [5, 4, 3], [5, 6, 4], [7, 6, 5], [7, 0, 6], [2, 0, 7], [1, 6, 0], [4, 6, 1], [3, 2, 7], [5, 3, 7], [10, 9, 8], [11, 10, 8], [11, 8, 12], [13, 11, 12], [13, 12, 14], [15, 13, 14], [15, 14, 9], [10, 15, 9], [8, 14, 12], [8, 9, 14], [11, 13, 15], [11, 15, 10]], "pts": [[5.933484862999999, 5.926368164, 0.0], [6.237890137, 4.2, 0.0], [5.933484862999999, 5.926368164, 2.0], [6.237890137, 4.2, 2.0], [6.336370605, 4.217364746, 0.0], [6.336370605, 4.217364746, 2.0], [6.0319653319999995, 5.94373291, 0.0], [6.0319653319999995, 5.94373291, 2.0], [0.537890015, 4.2, 0.0], [0.8422952269999999, 5.926368164, 0.0], [0.8422952269999999, 5.926368164, 2.0], [0.537890015, 4.2, 2.0], [0.43940921, 4.217364746, 0.0], [0.43940921, 4.217364746, 2.0], [0.7438144529999999, 5.94373291, 0.0], [0.7438144529999999, 5.94373291, 2.0]], "color": [137, 207, 240], "sides": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "sources": [{"name": "S1", "xyz": [1.7438900000000002, 6.855428571428572, 1.3940000000000001]}, {"name": "S2", "xyz": [5.03189, 6.855428571428572, 1.3940000000000001]}], "receivers": [{"name": "R1", "xyz": [3.38789, 5.007937043785338, 1.2]}, {"name": "R2", "xyz": [3.38789, 4.007937043785338, 1.2]}, {"name": "R3", "xyz": [3.38789, 4.207937043785338, 1.2]}, {"name": "R4", "xyz": [3.38789, 4.4079370437853385, 1.2]}, {"name": "R5", "xyz": [3.38789, 4.607937043785338, 1.2]}, {"name": "R6", "xyz": [3.38789, 4.807937043785338, 1.2]}]} diff --git a/data/models/Studio/Studio.py b/data/models/Studio/Studio.py new file mode 100644 index 0000000..febf0c1 --- /dev/null +++ b/data/models/Studio/Studio.py @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Tobias Hienzsch + +from pffdtd.geometry.math import find_third_vertex +from pffdtd.sim3d.model_builder import RoomModelBuilder +from pffdtd.sim3d.setup import Setup3D + + +class Studio(Setup3D): + model_file = 'model.json' + mat_folder = '../../materials' + source_index = 1 + source_signal = 'impulse' + diff_source = True + materials = { + 'Absorber M': 'absorber_8000_100mm.h5', + 'Absorber L': 'absorber_8000_200mm.h5', + 'Ceiling': 'concrete_painted.h5', + 'Diffusor': 'door_wood.h5', + 'Floor': 'floor_wood.h5', + 'Sofa': 'absorber_8000_100mm.h5', + 'Speaker_Cabinet': 'door_wood.h5', + 'Table': 'door_wood.h5', + 'Walls': 'concrete_painted.h5', + } + duration = 2.0 + Tc = 20 + rh = 50 + fcc = False + ppw = 10.5 + fmax = 800.0 + save_folder = '../../sim_data/Studio/cpu' + save_folder_gpu = '../../sim_data/Studio/gpu' + draw_vox = True + draw_backend = 'polyscope' + compress = 0 + rot_az_el = [0, 0] + + def generate_model(self, constants): + S = 0.90 + L = 7.00*S + W = 5.19*S + H = 3.70*S + + src_height = 1.2 + src_backwall = 1 + src_distance = 2.2 + src_left = [W/2-src_distance/2, L-src_backwall, src_height] + src_right = [W/2+src_distance/2, L-src_backwall, src_height] + + l1, l2 = find_third_vertex(src_left, src_right) + listener = l1 if l1[1] < l2[1] else l2 + + p1 = listener.copy() + p1[2] = 0.9 + + p2 = p1.copy() + p2[2] = 1.5 + + p3 = p1.copy() + p3[2] = 2.0 + + room = RoomModelBuilder(L, W, H) + room.with_colors({ + 'Absorber M': [111, 55, 10], + 'Absorber L': [111, 55, 10], + 'Ceiling': [200, 200, 200], + 'Diffusor': [140, 80, 35], + 'Floor': [151, 134, 122], + 'Table': [130, 75, 25], + 'Sofa': [25, 25, 25], + 'Speaker_Cabinet': [15, 15, 15], + 'Walls': [255, 255, 255], + }) + + room.add_box('Absorber M', [0.1, 2.5, 1.5], [0.2, L-2.5-0.75, 0.75]) + room.add_box('Absorber M', [0.1, 2.5, 1.5], [W-0.1-0.2, L-2.5-0.75, 0.75]) + + room.add_box('Absorber L', [1.0, 0.2, 2.5], [src_left[0]-1.0/2, L-0.2-0.1, 0.25]) + room.add_box('Absorber L', [1.0, 0.2, 2.5], [src_right[0]-1.0/2, L-0.2-0.1, 0.25]) + + room.add_box('Absorber L', [2.5, 2.0, 0.2], [W/2-2.5/2, L-2-0.5, H-0.3]) + room.add_box('Absorber L', [2.5, 2.0, 0.2], [W/2-2.5/2, L-2-2.5-0.5, H-0.3]) + + room.add_diffusor_1d([2, 15*0.0254, 1.5], [W/2-1, 0.05, 0.7], 3*0.0254) + room.add_box('Absorber L', [1.0, 0.2, 2.5], [0.1, 0.1, 0.25]) + room.add_box('Absorber L', [1.0, 0.2, 2.5], [W-1.0-0.1, 0.1, 0.25]) + + room.add_box('Sofa', [2.52, 0.98, 0.48], [W/2-2.52/2, 0.4, 0.05]) + room.add_box('Table', [1.8, 0.8, 0.02], [W/2-1.8/2, listener[1]+0.4, 0.7]) + + speaker_box = [0.435, 0.490, 0.650] + speaker_mid = [0.217, -0.075, 0.520] + room.add_cabinet_speaker('Speaker Left', src_left, speaker_box, speaker_mid) + room.add_cabinet_speaker('Speaker Right', src_right, speaker_box, speaker_mid) + # room.add_cabinet_speaker("Speaker Left", src_left) + # room.add_cabinet_speaker("Speaker Right", src_right) + # room.add_source("Speaker Left", src_left) + # room.add_source("Speaker Right", src_right) + room.add_receiver('Engineer', listener.tolist()) + room.add_receiver('P1', p1.tolist()) + room.add_receiver('P2', p2.tolist()) + room.add_receiver('P3', p3.tolist()) + room.build(self.model_file) diff --git a/data/models/Studio/Studio_cpu.py b/data/models/Studio/Studio_cpu.py deleted file mode 100644 index f751329..0000000 --- a/data/models/Studio/Studio_cpu.py +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.sim3d.setup import sim_setup_3d - -sim_setup_3d( - model_json_file='model.json', - mat_folder='../../materials', - source_num=1, - insig_type='impulse', - diff_source=True, - mat_files_dict={ - 'Absorber M': 'absorber_8000_100mm.h5', - 'Absorber L': 'absorber_8000_200mm.h5', - 'Ceiling': 'concrete_painted.h5', - 'Diffusor': 'door_wood.h5', - 'Floor': 'floor_wood.h5', - 'Sofa': 'absorber_8000_100mm.h5', - 'Speaker_Cabinet': 'door_wood.h5', - 'Table': 'door_wood.h5', - 'Walls': 'concrete_painted.h5', - }, - duration=2.0, - Tc=20, - rh=50, - fcc_flag=False, - PPW=10.5, - fmax=800.0, - save_folder='../../sim_data/Studio/cpu', - save_folder_gpu='../../sim_data/Studio/gpu', - draw_vox=True, - draw_backend='polyscope', - compress=0, - rot_az_el=[0, 0], -) diff --git a/data/models/Studio/Studio_model.py b/data/models/Studio/Studio_model.py deleted file mode 100644 index 0e255cc..0000000 --- a/data/models/Studio/Studio_model.py +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2024 Tobias Hienzsch - -from pffdtd.geometry.math import find_third_vertex -from pffdtd.sim3d.model_builder import RoomModelBuilder - -S = 0.90 -L = 7.00*S -W = 5.19*S -H = 3.70*S - -src_height = 1.2 -src_backwall = 1 -src_distance = 2.2 -src_left = [W/2-src_distance/2, L-src_backwall, src_height] -src_right = [W/2+src_distance/2, L-src_backwall, src_height] - -l1, l2 = find_third_vertex(src_left, src_right) -listener = l1 if l1[1] < l2[1] else l2 - -p1 = listener.copy() -p1[2] = 0.9 - -p2 = p1.copy() -p2[2] = 1.5 - -p3 = p1.copy() -p3[2] = 2.0 - -room = RoomModelBuilder(L, W, H) -room.with_colors({ - "Absorber M": [111, 55, 10], - "Absorber L": [111, 55, 10], - "Ceiling": [200, 200, 200], - "Diffusor": [140, 80, 35], - "Floor": [151, 134, 122], - "Table": [130, 75, 25], - "Sofa": [25, 25, 25], - "Speaker_Cabinet": [15, 15, 15], - "Walls": [255, 255, 255], -}) - -room.add_box("Absorber M", [0.1, 2.5, 1.5], [0.2, L-2.5-0.75, 0.75]) -room.add_box("Absorber M", [0.1, 2.5, 1.5], [W-0.1-0.2, L-2.5-0.75, 0.75]) - -room.add_box("Absorber L", [1.0, 0.2, 2.5], [src_left[0]-1.0/2, L-0.2-0.1, 0.25]) -room.add_box("Absorber L", [1.0, 0.2, 2.5], [src_right[0]-1.0/2, L-0.2-0.1, 0.25]) - -room.add_box("Absorber L", [2.5, 2.0, 0.2], [W/2-2.5/2, L-2-0.5, H-0.3]) -room.add_box("Absorber L", [2.5, 2.0, 0.2], [W/2-2.5/2, L-2-2.5-0.5, H-0.3]) - -room.add_diffusor_1d([2, 15*0.0254, 1.5], [W/2-1, 0.05, 0.7], 3*0.0254) -room.add_box("Absorber L", [1.0, 0.2, 2.5], [0.1, 0.1, 0.25]) -room.add_box("Absorber L", [1.0, 0.2, 2.5], [W-1.0-0.1, 0.1, 0.25]) - -room.add_box("Sofa", [2.52, 0.98, 0.48], [W/2-2.52/2, 0.4, 0.05]) -room.add_box("Table", [1.8, 0.8, 0.02], [W/2-1.8/2, listener[1]+0.4, 0.7]) - -speaker_box = [0.435, 0.490, 0.650] -speaker_mid = [0.217, -0.075, 0.520] -room.add_cabinet_speaker("Speaker Left", src_left, speaker_box, speaker_mid) -room.add_cabinet_speaker("Speaker Right", src_right, speaker_box, speaker_mid) -# room.add_cabinet_speaker("Speaker Left", src_left) -# room.add_cabinet_speaker("Speaker Right", src_right) -# room.add_source("Speaker Left", src_left) -# room.add_source("Speaker Right", src_right) - -room.add_receiver("Engineer", listener.tolist()) -room.add_receiver("P1", p1.tolist()) -room.add_receiver("P2", p2.tolist()) -room.add_receiver("P3", p3.tolist()) - -model_file = 'model.json' -room.build(model_file) diff --git a/doc/build.md b/doc/build.md index 07b5af5..acd58b0 100644 --- a/doc/build.md +++ b/doc/build.md @@ -23,6 +23,7 @@ sudo apt install libomp-dev ninja-build # Ubuntu # Can be skipped if building with conan package manager sudo dnf install hdf5-devel fmt-devel cli11-devel # Fedora +sudo apt install libhdf5-dev libfmt-dev libcli11-dev # Ubuntu ``` ### GCC or Clang @@ -36,15 +37,21 @@ cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_PROJECT_TOP_LE Assumes that `AdaptiveCPP` was build with `clang++`: ```shell -cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=clang -D CMAKE_CXX_COMPILER=clang++ -D PFFDTD_ENABLE_ACPP_SYCL=ON -D AdaptiveCpp_DIR=/usr/local/lib/cmake/AdaptiveCpp -D ACPP_TARGETS="generic" -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake +cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=clang -D CMAKE_CXX_COMPILER=clang++ -D PFFDTD_ENABLE_SYCL_ACPP=ON -D AdaptiveCpp_DIR=/usr/local/lib/cmake/AdaptiveCpp -D ACPP_TARGETS="generic" -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake ``` ### Intel oneAPI +- +- Nvidia: + - Drivers + - + - + ```shell source /opt/intel/oneapi/setvars.sh -cmake -S. -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=icx -D CMAKE_CXX_COMPILER=icpx -D PFFDTD_ENABLE_INTEL_SYCL=ON -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake -D CONAN_HOST_PROFILE=../cmake/profile/linux_sycl +cmake -S. -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=icx -D CMAKE_CXX_COMPILER=icpx -D PFFDTD_ENABLE_SYCL_ONEAPI=ON -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake -D CONAN_HOST_PROFILE=../cmake/profile/linux_sycl cmake --build build # Using hyper-threads is usally a slow down. Use the number of physical cores. @@ -54,6 +61,12 @@ export DPCPP_CPU_NUM_CUS=16 ./run_2d.sh ``` +### CUDA + +```shell +cmake -S. -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -D PFFDTD_ENABLE_CUDA=ON -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake +``` + ## Windows ```shell @@ -79,5 +92,5 @@ Currently not supported. Has issues building HDF5 from source via conan. # Or: Set environment variables . 'C:\Program Files (x86)\Intel\oneAPI\setvars.bat' -cmake -S . -B build -G Ninja -D PFFDTD_ENABLE_INTEL_SYCL=ON -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=icx -D CMAKE_CXX_COMPILER=icx -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake -D CONAN_HOST_PROFILE=../cmake/profile/windows_sycl +cmake -S . -B build -G Ninja -D PFFDTD_ENABLE_SYCL_ONEAPI=ON -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=icx -D CMAKE_CXX_COMPILER=icx -D CMAKE_PROJECT_TOP_LEVEL_INCLUDES=external/cmake-conan/conan_provider.cmake -D CONAN_HOST_PROFILE=../cmake/profile/windows_sycl ``` diff --git a/pyproject.toml b/pyproject.toml index 00610a5..676468d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,3 +62,10 @@ addopts = ["-ra", "--strict-markers", "--strict-config"] xfail_strict = true filterwarnings = ["error"] testpaths = ["src/python/test/*"] + +[tool.autopep8] +max_line_length = 200 +ignore = [] +in-place = true +recursive = true +aggressive = 0 diff --git a/run_2d.sh b/run_2d.sh index 5c5b071..0129c2b 100755 --- a/run_2d.sh +++ b/run_2d.sh @@ -29,7 +29,7 @@ cd "$model_dir" python "$sim_setup" # Run sim -DPCPP_CPU_PLACES=cores DPCPP_CPU_CU_AFFINITY=spread DPCPP_CPU_NUM_CUS=$jobs OMP_NUM_THREADS=$jobs "$engine_exe" -s "$sim_dir" +DPCPP_CPU_PLACES=cores DPCPP_CPU_CU_AFFINITY=spread DPCPP_CPU_NUM_CUS=$jobs OMP_NUM_THREADS=$jobs "$engine_exe" -s "$sim_dir" -e sycl # pffdtd sim2d run --sim_dir "$sim_dir" --video # Post-process diff --git a/run_3d.sh b/run_3d.sh index b9ed994..d0ba852 100755 --- a/run_3d.sh +++ b/run_3d.sh @@ -7,40 +7,37 @@ set -e root_dir="$(cd "$(dirname "$0")" && pwd)" engine_exe="$root_dir/build/src/cpp/main_3d/pffdtd_3d" +engine_exe="$root_dir/cmake-build-cuda/src/cpp/main_3d/pffdtd_3d" -sim_name="LivingRoom" -sim_setup="${sim_name}_cpu.py" -sim_model_gen="${sim_name}_model.py" -sim_dir="$root_dir/data/sim_data/$sim_name/cpu" +sim_name="ProStudio" +sim_setup="${sim_name}.py" +sim_dir="$root_dir/data/sim_data/$sim_name/gpu" model_dir="$root_dir/data/models/$sim_name" materials_dir="$root_dir/data/materials" -fmin=25 -fmax=800 +fmin=20 +fmax=4000 smoothing=0 # Delete old sim rm -rf "$sim_dir" -# Generate model +# Generate materials, model & sim data cd "$model_dir" -python "$sim_model_gen" - -# Generate sim data pffdtd materials build "$materials_dir" -python "$sim_setup" +pffdtd sim3d setup "$sim_setup" # Run sim $engine_exe "$sim_dir" # Post-process -pffdtd sim3d process-outputs --sim_dir="$sim_dir" --fcut_lowpass "$fmax" --order_lowpass=8 --symmetric_lowpass --fcut_lowcut "$fmin" --order_lowcut=4 --air_abs_filter="stokes" --save_wav --plot +pffdtd sim3d process-outputs --sim_dir="$sim_dir" --fcut_lowpass "$fmax" --order_lowpass=8 --symmetric_lowpass --fcut_lowcut "$fmin" --order_lowcut=4 --air_abs_filter="none" --save_wav --plot pffdtd analysis response --fmin=10 --target="-2.5" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R002_out_normalised.wav -# pffdtd analysis response --fmin=10 --target="-2.0" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R003_out_normalised.wav -# pffdtd analysis response --fmin=10 --target="-1.5" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R004_out_normalised.wav -# pffdtd analysis response --fmin=10 --target="-1.0" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R005_out_normalised.wav -# pffdtd analysis response --fmin=10 --target="-0.5" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R006_out_normalised.wav +pffdtd analysis response --fmin=10 --target="-2.0" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R003_out_normalised.wav +pffdtd analysis response --fmin=10 --target="-1.5" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R004_out_normalised.wav +pffdtd analysis response --fmin=10 --target="-1.0" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R005_out_normalised.wav +pffdtd analysis response --fmin=10 --target="-0.5" --smoothing=$smoothing --fmax=$fmax $sim_dir/R001_out_normalised.wav $sim_dir/R006_out_normalised.wav pffdtd analysis waterfall $sim_dir/R001_out_normalised.wav pffdtd analysis t60 --fmin=$fmin --fmax="$fmax" --target=0.3 $sim_dir/R001_out_normalised.wav pffdtd analysis t60 --sim_dir="$sim_dir" --fmin=$fmin --fmax="$fmax" --target=0.25 diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt index 469d625..d87820d 100644 --- a/src/cpp/CMakeLists.txt +++ b/src/cpp/CMakeLists.txt @@ -6,7 +6,7 @@ project(pffdtd) add_library(pffdtd) add_library(pffdtd::pffdtd ALIAS pffdtd) target_include_directories(pffdtd PUBLIC ${PROJECT_SOURCE_DIR}) -target_compile_definitions(pffdtd PUBLIC PRECISION=2) +target_compile_definitions(pffdtd PUBLIC PFFDTD_PRECISION=2) target_link_libraries(pffdtd PUBLIC @@ -35,12 +35,12 @@ target_sources(pffdtd target_compile_definitions(pffdtd PUBLIC _CRT_SECURE_NO_WARNINGS=1) -if(PFFDTD_ENABLE_ACPP_SYCL OR PFFDTD_ENABLE_INTEL_SYCL) +if(PFFDTD_HAS_SYCL) target_compile_definitions(pffdtd PUBLIC PFFDTD_HAS_SYCL=1) endif() if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")) - target_compile_options(pffdtd PRIVATE /W3) + target_compile_options(pffdtd PUBLIC /W3) else () - target_compile_options(pffdtd PRIVATE -Wno-deprecated-declarations) + target_compile_options(pffdtd PUBLIC -Wall -Wextra -Wno-deprecated-declarations) endif () diff --git a/src/cpp/main_2d/CMakeLists.txt b/src/cpp/main_2d/CMakeLists.txt index 6824e10..0ec3a0a 100644 --- a/src/cpp/main_2d/CMakeLists.txt +++ b/src/cpp/main_2d/CMakeLists.txt @@ -6,7 +6,12 @@ project(pffdtd_2d) add_executable(pffdtd_2d main.cpp engine_native.cpp) target_link_libraries(pffdtd_2d PRIVATE CLI11::CLI11 pffdtd::pffdtd) -if(PFFDTD_ENABLE_ACPP_SYCL OR PFFDTD_ENABLE_INTEL_SYCL) +if(PFFDTD_HAS_SYCL) target_sources(pffdtd_2d PRIVATE engine_sycl.cpp) add_sycl_to_target(TARGET pffdtd_2d SOURCES engine_sycl.cpp) endif() + +if(PFFDTD_ENABLE_SYCL_ONEAPI) + set_source_files_properties(engine_sycl.cpp PROPERTIES COMPILE_FLAGS "-fsycl-targets=nvptx64-nvidia-cuda") + target_link_libraries(pffdtd_2d PRIVATE "-fsycl-targets=nvptx64-nvidia-cuda") +endif() diff --git a/src/cpp/main_2d/engine_native.cpp b/src/cpp/main_2d/engine_native.cpp index f1e28e8..9a71b8a 100644 --- a/src/cpp/main_2d/engine_native.cpp +++ b/src/cpp/main_2d/engine_native.cpp @@ -13,17 +13,13 @@ namespace pffdtd { -[[nodiscard]] constexpr auto to_ixy( - std::integral auto x, - std::integral auto y, - std::integral auto /*Nx*/, - std::integral auto Ny -) -> std::integral auto { +[[nodiscard]] constexpr auto +to_ixy(std::integral auto x, std::integral auto y, std::integral auto /*Nx*/, std::integral auto Ny) -> std::integral + auto { return x * Ny + y; } -auto EngineNative::operator()(Simulation2D const& sim) const - -> stdex::mdarray> { +auto EngineNative::operator()(Simulation2D const& sim) const -> stdex::mdarray> { auto const Nx = sim.Nx; auto const Ny = sim.Ny; @@ -34,7 +30,7 @@ auto EngineNative::operator()(Simulation2D const& sim) const auto const Nr = sim.out_ixy.size(); auto const lossFactor = sim.loss_factor; - pffdtd::summary(sim); + summary(sim); auto u0_buf = stdex::mdarray>(Nx, Ny); auto u1_buf = stdex::mdarray>(Nx, Ny); @@ -51,9 +47,9 @@ auto EngineNative::operator()(Simulation2D const& sim) const fmt::print(stdout, "{:04d}/{:04d}", n, Nt); std::fflush(stdout); - // Air Update - #pragma omp parallel for - for (int64_t x = 1; x < Nx-1; ++x) { +// Air Update +#pragma omp parallel for + for (int64_t x = 1; x < Nx - 1; ++x) { for (int64_t y = 1; y < Ny - 1; ++y) { auto const idx = to_ixy(x, y, 0, Ny); auto const left = u1.data_handle()[idx - 1]; diff --git a/src/cpp/main_2d/engine_native.hpp b/src/cpp/main_2d/engine_native.hpp index 94a3543..4a02367 100644 --- a/src/cpp/main_2d/engine_native.hpp +++ b/src/cpp/main_2d/engine_native.hpp @@ -11,8 +11,7 @@ namespace pffdtd { struct EngineNative { - [[nodiscard]] auto operator()(Simulation2D const& sim) const - -> stdex::mdarray>; + [[nodiscard]] auto operator()(Simulation2D const& sim) const -> stdex::mdarray>; }; } // namespace pffdtd diff --git a/src/cpp/main_2d/engine_sycl.cpp b/src/cpp/main_2d/engine_sycl.cpp index 2432704..36d4b6a 100644 --- a/src/cpp/main_2d/engine_sycl.cpp +++ b/src/cpp/main_2d/engine_sycl.cpp @@ -11,23 +11,13 @@ namespace pffdtd { -[[nodiscard]] constexpr auto to_ixy( - std::integral auto x, - std::integral auto y, - std::integral auto /*Nx*/, - std::integral auto Ny -) -> std::integral auto { - return x * Ny + y; -} +namespace { -static auto kernelAirUpdate( - sycl::id<2> id, - double* u0, - double const* u1, - double const* u2, - uint8_t const* inMask, - int64_t Ny -) -> void { +[[nodiscard]] constexpr auto to_ixy(auto x, auto y, auto /*Nx*/, auto Ny) { return x * Ny + y; } + +template +auto kernelAirUpdate(sycl::id<2> id, Float* u0, Float const* u1, Float const* u2, uint8_t const* inMask, int64_t Ny) + -> void { auto const x = id.get(0) + 1; auto const y = id.get(1) + 1; auto const idx = to_ixy(x, y, 0, Ny); @@ -42,14 +32,15 @@ static auto kernelAirUpdate( auto const top = u1[idx + Ny]; auto const last = u2[idx]; - u0[idx] = 0.5 * (left + right + bottom + top) - last; + u0[idx] = Float(0.5) * (left + right + bottom + top) - last; } -static auto kernelBoundaryRigid( +template +auto kernelBoundaryRigid( sycl::id<1> idx, - double* u0, - double const* u1, - double const* u2, + Float* u0, + Float const* u1, + Float const* u2, int64_t const* bn_ixy, int64_t const* adj_bn, int64_t Ny @@ -66,16 +57,17 @@ static auto kernelBoundaryRigid( auto const top = u1[ib + Ny]; auto const neighbors = left + right + top + bottom; - u0[ib] = (2 - 0.5 * K) * last1 + 0.5 * neighbors - last2; + u0[ib] = (Float(2) - Float(0.5) * K) * last1 + Float(0.5) * neighbors - last2; } -static auto kernelBoundaryLoss( +template +auto kernelBoundaryLoss( sycl::id<1> idx, - double* u0, - double const* u2, + Float* u0, + Float const* u2, int64_t const* bn_ixy, int64_t const* adj_bn, - double lossFactor + Float lossFactor ) -> void { auto const ib = bn_ixy[idx]; auto const K = adj_bn[idx]; @@ -83,13 +75,15 @@ static auto kernelBoundaryLoss( auto const prev = u2[ib]; auto const K4 = 4 - K; - u0[ib] = (current + lossFactor * K4 * prev) / (1 + lossFactor * K4); + u0[ib] = (current + lossFactor * K4 * prev) / (Float(1) + lossFactor * K4); } +} // namespace -auto EngineSYCL::operator()(Simulation2D const& sim) const - -> stdex::mdarray> { +auto EngineSYCL::operator()(Simulation2D const& sim) const -> stdex::mdarray> { - for (auto device : sycl::device::get_devices()) { pffdtd::summary(device); } + for (auto device : sycl::device::get_devices()) { + summary(device); + } auto const Nx = sim.Nx; auto const Ny = sim.Ny; @@ -100,12 +94,12 @@ auto EngineSYCL::operator()(Simulation2D const& sim) const auto const Nr = sim.out_ixy.size(); auto const loss_factor = sim.loss_factor; - pffdtd::summary(sim); + summary(sim); auto prop = sycl::property_list{sycl::property::queue::in_order()}; auto queue = sycl::queue{prop}; auto device = queue.get_device(); - pffdtd::summary(device); + summary(device); auto u0 = sycl::buffer(sycl::range<2>(Nx, Ny)); auto u1 = sycl::buffer(sycl::range<2>(Nx, Ny)); @@ -133,14 +127,7 @@ auto EngineSYCL::operator()(Simulation2D const& sim) const auto airRange = sycl::range<2>(Nx - 2, Ny - 2); cgh.parallel_for(airRange, [=](sycl::id<2> id) { - kernelAirUpdate( - id, - getPtr(u0a), - getPtr(u1a), - getPtr(u2a), - getPtr(inMaskAcc), - Ny - ); + kernelAirUpdate(id, getPtr(u0a), getPtr(u1a), getPtr(u2a), getPtr(inMaskAcc), Ny); }); }); @@ -153,15 +140,7 @@ auto EngineSYCL::operator()(Simulation2D const& sim) const auto rigidRange = sycl::range<1>(Nb); cgh.parallel_for(rigidRange, [=](sycl::id<1> id) { - kernelBoundaryRigid( - id, - getPtr(u0a), - getPtr(u1a), - getPtr(u2a), - getPtr(bn_ixy_acc), - getPtr(adj_bn_acc), - Ny - ); + kernelBoundaryRigid(id, getPtr(u0a), getPtr(u1a), getPtr(u2a), getPtr(bn_ixy_acc), getPtr(adj_bn_acc), Ny); }); }); @@ -173,23 +152,14 @@ auto EngineSYCL::operator()(Simulation2D const& sim) const auto lossRange = sycl::range<1>(Nb); cgh.parallel_for(lossRange, [=](sycl::id<1> id) { - kernelBoundaryLoss( - id, - getPtr(u0a), - getPtr(u2a), - getPtr(bn_ixy_acc), - getPtr(adj_bn_acc), - loss_factor - ); + kernelBoundaryLoss(id, getPtr(u0a), getPtr(u2a), getPtr(bn_ixy_acc), getPtr(adj_bn_acc), loss_factor); }); }); queue.submit([&](sycl::handler& cgh) { auto u0a = sycl::accessor{u0, cgh, sycl::read_write}; auto src_sig_acc = sycl::accessor{src_sig, cgh, sycl::read_only}; - cgh.parallel_for(sycl::range<1>(1), [=](sycl::id<1>) { - u0a[inx][iny] += src_sig_acc[n]; - }); + cgh.parallel_for(sycl::range<1>(1), [=](sycl::id<1>) { u0a[inx][iny] += src_sig_acc[n]; }); }); queue.submit([&](sycl::handler& cgh) { diff --git a/src/cpp/main_2d/engine_sycl.hpp b/src/cpp/main_2d/engine_sycl.hpp index 564ac8b..8dfebfe 100644 --- a/src/cpp/main_2d/engine_sycl.hpp +++ b/src/cpp/main_2d/engine_sycl.hpp @@ -11,8 +11,7 @@ namespace pffdtd { struct EngineSYCL { - [[nodiscard]] auto operator()(Simulation2D const& sim) const - -> stdex::mdarray>; + [[nodiscard]] auto operator()(Simulation2D const& sim) const -> stdex::mdarray>; }; } // namespace pffdtd diff --git a/src/cpp/main_2d/main.cpp b/src/cpp/main_2d/main.cpp index 885b10e..6ec8f90 100644 --- a/src/cpp/main_2d/main.cpp +++ b/src/cpp/main_2d/main.cpp @@ -22,11 +22,11 @@ struct Arguments { std::string engine{"native"}; - std::string simDir{}; + std::string simDir; std::string out{"out.h5"}; }; -int main(int argc, char** argv) { +auto main(int argc, char** argv) -> int { auto app = CLI::App{"pffdtd-2d"}; auto args = Arguments{}; app.add_option("-e,--engine", args.engine); @@ -42,7 +42,8 @@ int main(int argc, char** argv) { fmt::println("Using engine: NATIVE"); auto const engine = pffdtd::EngineNative{}; return engine(sim); - } else if (args.engine == "sycl") { + } + if (args.engine == "sycl") { #if defined(PFFDTD_HAS_SYCL) fmt::println("Using engine: SYCL"); auto const engine = pffdtd::EngineSYCL{}; @@ -50,9 +51,9 @@ int main(int argc, char** argv) { #else pffdtd::raisef("pffdtd built without SYCL support"); #endif - } else { - pffdtd::raisef("invalid engine '{}'", args.engine); } + + pffdtd::raisef("invalid engine '{}'", args.engine); }(); auto results = pffdtd::H5FWriter{simDir / args.out}; diff --git a/src/cpp/main_3d/CMakeLists.txt b/src/cpp/main_3d/CMakeLists.txt index ff0b540..cb560c9 100644 --- a/src/cpp/main_3d/CMakeLists.txt +++ b/src/cpp/main_3d/CMakeLists.txt @@ -4,7 +4,7 @@ project(pffdtd_3d) add_executable(pffdtd_3d) -target_compile_definitions(pffdtd_3d PRIVATE USING_CUDA=0) +target_link_libraries(pffdtd_3d PRIVATE pffdtd::pffdtd) target_sources(pffdtd_3d PRIVATE engine_cpu.cpp @@ -12,4 +12,9 @@ target_sources(pffdtd_3d main.cpp ) -target_link_libraries(pffdtd_3d PRIVATE pffdtd::pffdtd) +if(PFFDTD_ENABLE_CUDA) + target_sources(pffdtd_3d PRIVATE engine_gpu.cu engine_gpu.hpp) + target_compile_definitions(pffdtd_3d PRIVATE PFFDTD_HAS_CUDA=1) +else() + target_compile_definitions(pffdtd_3d PRIVATE PFFDTD_HAS_CUDA=0) +endif() diff --git a/src/cpp/main_3d/engine_cpu.cpp b/src/cpp/main_3d/engine_cpu.cpp index d700f01..39de9a9 100644 --- a/src/cpp/main_3d/engine_cpu.cpp +++ b/src/cpp/main_3d/engine_cpu.cpp @@ -11,43 +11,102 @@ #include -#include #include #include #include namespace pffdtd { +namespace { + +// function that does freq-dep RLC boundaries. See 2016 ISMRA paper and +// accompanying webpage (slightly improved here) +template +auto process_bnl_pts_fd( + Float* u0b, + Float const* u2b, + Float const* ssaf_bnl, + int8_t const* mat_bnl, + int64_t Nbl, + int8_t const* Mb, + Float lo2, + Float* vh1, + Float* gh1, + MatQuad const* mat_quads, + Float const* mat_beta +) -> double { + auto const start = omp_get_wtime(); +#pragma omp parallel for schedule(static) + for (int64_t nb = 0; nb < Nbl; nb++) { + Float _1 = 1.0; + Float _2 = 2.0; + int32_t const k = mat_bnl[nb]; + + Float lo2Kbg = lo2 * ssaf_bnl[nb] * mat_beta[k]; + Float fac = _2 * lo2 * ssaf_bnl[nb] / (_1 + lo2Kbg); + + Float u0bint = u0b[nb]; + Float u2bint = u2b[nb]; + + u0bint = (u0bint + lo2Kbg * u2bint) / (_1 + lo2Kbg); + + Float vh1nb[MMb]; + for (int8_t m = 0; m < Mb[k]; m++) { + int64_t const nbm = nb * MMb + m; + int32_t const mbk = k * MMb + m; + MatQuad const* tm = &(mat_quads[mbk]); + vh1nb[m] = vh1[nbm]; + u0bint -= fac * (_2 * (tm->bDh) * vh1nb[m] - (tm->bFh) * gh1[nbm]); + } + + Float du = u0bint - u2bint; + + for (int8_t m = 0; m < Mb[k]; m++) { + int64_t const nbm = nb * MMb + m; + int32_t const mbk = k * MMb + m; + MatQuad const* tm = &(mat_quads[mbk]); + Float vh0nbm = (tm->b) * du + (tm->bd) * vh1nb[m] - _2 * (tm->bFh) * gh1[nbm]; + gh1[nbm] += (vh0nbm + vh1nb[m]) / _2; + vh1[nbm] = vh0nbm; + } + + u0b[nb] = u0bint; + } + return omp_get_wtime() - start; +} + +} // namespace + auto run(Simulation3D& sd) -> double { // keep local ints, scalars - int64_t Ns = sd.Ns; - int64_t Nr = sd.Nr; - int64_t Nt = sd.Nt; - int64_t Npts = sd.Npts; - int64_t Nx = sd.Nx; - int64_t Ny = sd.Ny; - int64_t Nz = sd.Nz; - int64_t Nb = sd.Nb; - int64_t Nbl = sd.Nbl; - int64_t Nba = sd.Nba; - int8_t* Mb = sd.Mb; + int64_t const Ns = sd.Ns; + int64_t const Nr = sd.Nr; + int64_t const Nt = sd.Nt; + int64_t const Npts = sd.Npts; + int64_t const Nx = sd.Nx; + int64_t const Ny = sd.Ny; + int64_t const Nz = sd.Nz; + int64_t const Nb = sd.Nb; + int64_t const Nbl = sd.Nbl; + int64_t const Nba = sd.Nba; + int8_t* Mb = sd.Mb; // keep local copies of pointers (style choice) - int64_t* bn_ixyz = sd.bn_ixyz; - int64_t* bnl_ixyz = sd.bnl_ixyz; - int64_t* bna_ixyz = sd.bna_ixyz; - int64_t* in_ixyz = sd.in_ixyz; - int64_t* out_ixyz = sd.out_ixyz; - uint16_t* adj_bn = sd.adj_bn; - uint8_t* bn_mask = sd.bn_mask; - int8_t* mat_bnl = sd.mat_bnl; - int8_t* Q_bna = sd.Q_bna; - double* in_sigs = sd.in_sigs; - double* u_out = sd.u_out; - int8_t fcc_flag = sd.fcc_flag; - Real* ssaf_bnl = sd.ssaf_bnl; - Real* mat_beta = sd.mat_beta; - MatQuad* mat_quads = sd.mat_quads; + int64_t* bn_ixyz = sd.bn_ixyz; + int64_t* bnl_ixyz = sd.bnl_ixyz; + int64_t* bna_ixyz = sd.bna_ixyz; + int64_t* in_ixyz = sd.in_ixyz; + int64_t* out_ixyz = sd.out_ixyz; + uint16_t* adj_bn = sd.adj_bn; + uint8_t* bn_mask = sd.bn_mask; + int8_t* mat_bnl = sd.mat_bnl; + int8_t* Q_bna = sd.Q_bna; + double* in_sigs = sd.in_sigs; + double* u_out = sd.u_out; + int8_t const fcc_flag = sd.fcc_flag; + Real* ssaf_bnl = sd.ssaf_bnl; + Real* mat_beta = sd.mat_beta; + MatQuad* mat_quads = sd.mat_quads; // allocate memory auto u0_buf = std::vector(static_cast(Npts)); @@ -69,28 +128,28 @@ auto run(Simulation3D& sd) -> double { Real* gh1 = gh1_buf.data(); // sim coefficients - Real lo2 = sd.lo2; - Real sl2 = sd.sl2; - Real l = sd.l; - Real a1 = sd.a1; - Real a2 = sd.a2; + Real const lo2 = sd.lo2; + Real const sl2 = sd.sl2; + Real const l = sd.l; + Real const a1 = sd.a1; + Real const a2 = sd.a2; // can control outside with OMP_NUM_THREADS env variable - int numWorkers = omp_get_max_threads(); + int const numWorkers = omp_get_max_threads(); fmt::println("ENGINE: fcc_flag={}", fcc_flag); fmt::println("{}", (fcc_flag > 0) ? "fcc=true" : "fcc=false"); // for timing - double timeElapsed; - double timeElapsedAir = 0.0; - double timeElapsedBn = 0.0; - double timeElapsedSample; + double timeElapsed = NAN; + double timeElapsedAir = 0.0; + double timeElapsedBn = 0.0; + double timeElapsedSample = NAN; double timeElapsedSample_air = 0.0; double timeElapsedSampleBn = 0.0; - double startTime = omp_get_wtime(); + double const startTime = omp_get_wtime(); - int64_t NzNy = Nz * Ny; + int64_t const NzNy = Nz * Ny; for (int64_t n = 0; n < Nt; n++) { auto const sampleStartTime = omp_get_wtime(); @@ -144,8 +203,8 @@ auto run(Simulation3D& sd) -> double { for (int64_t ix = 1; ix < Nx - 1; ix++) { for (int64_t iy = 1; iy < Ny - 1; iy++) { for (int64_t iz = 1; iz < Nz - 1; iz++) { // contiguous - int64_t ii = ix * NzNy + iy * Nz + iz; - if (!(GET_BIT(bn_mask[ii >> 3], ii % 8))) { + int64_t const ii = ix * NzNy + iy * Nz + iz; + if ((GET_BIT(bn_mask[ii >> 3], ii % 8)) == 0) { Real partial = a1 * u1[ii] - u0[ii]; partial += a2 * u1[ii + NzNy]; partial += a2 * u1[ii - NzNy]; @@ -165,8 +224,8 @@ auto run(Simulation3D& sd) -> double { // while loop iterates iterates over both types of FCC grids int64_t iz = (fcc_flag == 1) ? 2 - (ix + iy) % 2 : 1; while (iz < Nz - 1) { - int64_t ii = ix * NzNy + iy * Nz + iz; - if (!(GET_BIT(bn_mask[ii >> 3], ii % 8))) { + int64_t const ii = ix * NzNy + iy * Nz + iz; + if ((GET_BIT(bn_mask[ii >> 3], ii % 8)) == 0) { Real partial = a1 * u1[ii] - u0[ii]; partial += a2 * u1[ii + NzNy + Nz]; partial += a2 * u1[ii - NzNy - Nz]; @@ -189,9 +248,9 @@ auto run(Simulation3D& sd) -> double { } // ABC loss (2nd-order accurate first-order Engquist-Majda) for (int64_t nb = 0; nb < Nba; nb++) { - Real lQ = l * Q_bna[nb]; - int64_t ib = bna_ixyz[nb]; - u0[ib] = (u0[ib] + lQ * u2ba[nb]) / (1.0 + lQ); + Real const lQ = l * Q_bna[nb]; + int64_t const ib = bna_ixyz[nb]; + u0[ib] = (u0[ib] + lQ * u2ba[nb]) / (1.0 + lQ); } // rigid boundary nodes, using adj data @@ -200,19 +259,20 @@ auto run(Simulation3D& sd) -> double { if (fcc_flag == 0) { #pragma omp parallel for for (int64_t nb = 0; nb < Nb; nb++) { - int64_t ii = bn_ixyz[nb]; - uint8_t Kint; - uint16_t v = adj_bn[nb]; - for (Kint = 0; v; Kint++) + int64_t const ii = bn_ixyz[nb]; + uint8_t Kint = 0; + uint16_t v = adj_bn[nb]; + for (Kint = 0; v != 0U; Kint++) { v &= v - 1; // clear the least significant bit set + } - Real _2 = 2.0; - Real K = Kint; - Real b2 = a2; - Real b1 = (_2 - sl2 * K); + Real const _2 = 2.0; + Real const K = Kint; + Real const b2 = a2; + Real const b1 = (_2 - sl2 * K); - Real partial = b1 * u1[ii] - u0[ii]; - uint16_t adj = adj_bn[nb]; + Real partial = b1 * u1[ii] - u0[ii]; + uint16_t const adj = adj_bn[nb]; partial += b2 * (Real)GET_BIT(adj, 0) * u1[ii + NzNy]; partial += b2 * (Real)GET_BIT(adj, 1) * u1[ii - NzNy]; partial += b2 * (Real)GET_BIT(adj, 2) * u1[ii + Nz]; @@ -224,19 +284,20 @@ auto run(Simulation3D& sd) -> double { } else if (fcc_flag > 0) { #pragma omp parallel for for (int64_t nb = 0; nb < Nb; nb++) { - int64_t ii = bn_ixyz[nb]; - uint8_t Kint; - uint16_t v = adj_bn[nb]; - for (Kint = 0; v; Kint++) + int64_t const ii = bn_ixyz[nb]; + uint8_t Kint = 0; + uint16_t v = adj_bn[nb]; + for (Kint = 0; v != 0U; Kint++) { v &= v - 1; // clear the least significant bit set + } - Real _2 = 2.0; - Real K = Kint; - Real b2 = a2; - Real b1 = (_2 - sl2 * K); + Real const _2 = 2.0; + Real const K = Kint; + Real const b2 = a2; + Real const b1 = (_2 - sl2 * K); - Real partial = b1 * u1[ii] - u0[ii]; - uint16_t adj = adj_bn[nb]; + Real partial = b1 * u1[ii] - u0[ii]; + uint16_t const adj = adj_bn[nb]; partial += b2 * (Real)GET_BIT(adj, 0) * u1[ii + NzNy + Nz]; partial += b2 * (Real)GET_BIT(adj, 1) * u1[ii - NzNy - Nz]; partial += b2 * (Real)GET_BIT(adj, 2) * u1[ii + Nz + 1]; @@ -259,19 +320,7 @@ auto run(Simulation3D& sd) -> double { u0b[nb] = u0[bnl_ixyz[nb]]; } // process FD boundary nodes - timeElapsedSampleBn = process_bnl_pts_fd( - u0b, - u2b, - ssaf_bnl, - mat_bnl, - Nbl, - Mb, - lo2, - vh1, - gh1, - mat_quads, - mat_beta - ); + timeElapsedSampleBn = process_bnl_pts_fd(u0b, u2b, ssaf_bnl, mat_bnl, Nbl, Mb, lo2, vh1, gh1, mat_quads, mat_beta); timeElapsedBn += timeElapsedSampleBn; // write back #pragma omp parallel for @@ -281,21 +330,21 @@ auto run(Simulation3D& sd) -> double { // read output at current sample for (int64_t nr = 0; nr < Nr; nr++) { - int64_t ii = out_ixyz[nr]; + int64_t const ii = out_ixyz[nr]; u_out[nr * Nt + n] = (double)u1[ii]; } // add current sample to next (as per update) for (int64_t ns = 0; ns < Ns; ns++) { - int64_t ii = in_ixyz[ns]; + int64_t const ii = in_ixyz[ns]; u0[ii] += (Real)in_sigs[ns * Nt + n]; } // swap pointers - Real* tmp_ptr; - tmp_ptr = u1; - u1 = u0; - u0 = tmp_ptr; + Real* tmp_ptr = nullptr; + tmp_ptr = u1; + u1 = u0; + u0 = tmp_ptr; // using extra state here for simplicity tmp_ptr = u2b; @@ -307,7 +356,7 @@ auto run(Simulation3D& sd) -> double { timeElapsed = now - startTime; timeElapsedSample = now - sampleStartTime; - pffdtd::print_progress( + print_progress( n, Nt, Npts, @@ -330,81 +379,11 @@ auto run(Simulation3D& sd) -> double { /*------------------------ * RETURN ------------------------*/ - fmt::println( - "Air update: {:.6}s, {:.2} Mvox/s", - timeElapsedAir, - Npts * Nt / 1e6 / timeElapsedAir - ); - fmt::println( - "Boundary loop: {:.6}s, {:.2} Mvox/s", - timeElapsedBn, - Nb * Nt / 1e6 / timeElapsedBn - ); - fmt::println( - "Combined (total): {:.6}s, {:.2} Mvox/s", - timeElapsed, - Npts * Nt / 1e6 / timeElapsed - ); + fmt::println("Air update: {:.6}s, {:.2} Mvox/s", timeElapsedAir, Npts * Nt / 1e6 / timeElapsedAir); + fmt::println("Boundary loop: {:.6}s, {:.2} Mvox/s", timeElapsedBn, Nb * Nt / 1e6 / timeElapsedBn); + fmt::println("Combined (total): {:.6}s, {:.2} Mvox/s", timeElapsed, Npts * Nt / 1e6 / timeElapsed); return timeElapsed; } -// function that does freq-dep RLC boundaries. See 2016 ISMRA paper and -// accompanying webpage (slightly improved here) -double process_bnl_pts_fd( - Real* u0b, - Real const* u2b, - Real const* ssaf_bnl, - int8_t const* mat_bnl, - int64_t Nbl, - int8_t* Mb, - Real lo2, - Real* vh1, - Real* gh1, - MatQuad const* mat_quads, - Real const* mat_beta -) { - auto const start = omp_get_wtime(); -#pragma omp parallel for schedule(static) - for (int64_t nb = 0; nb < Nbl; nb++) { - Real _1 = 1.0; - Real _2 = 2.0; - int32_t k = mat_bnl[nb]; - - Real lo2Kbg = lo2 * ssaf_bnl[nb] * mat_beta[k]; - Real fac = _2 * lo2 * ssaf_bnl[nb] / (_1 + lo2Kbg); - - Real u0bint = u0b[nb]; - Real u2bint = u2b[nb]; - - u0bint = (u0bint + lo2Kbg * u2bint) / (_1 + lo2Kbg); - - Real vh1nb[MMb]; - for (int8_t m = 0; m < Mb[k]; m++) { - int64_t nbm = nb * MMb + m; - int32_t mbk = k * MMb + m; - MatQuad const* tm; - tm = &(mat_quads[mbk]); - vh1nb[m] = vh1[nbm]; - u0bint -= fac * (_2 * (tm->bDh) * vh1nb[m] - (tm->bFh) * gh1[nbm]); - } - - Real du = u0bint - u2bint; - - for (int8_t m = 0; m < Mb[k]; m++) { - int64_t nbm = nb * MMb + m; - int32_t mbk = k * MMb + m; - MatQuad const* tm; - tm = &(mat_quads[mbk]); - Real vh0nbm - = (tm->b) * du + (tm->bd) * vh1nb[m] - _2 * (tm->bFh) * gh1[nbm]; - gh1[nbm] += (vh0nbm + vh1nb[m]) / _2; - vh1[nbm] = vh0nbm; - } - - u0b[nb] = u0bint; - } - return omp_get_wtime() - start; -} - } // namespace pffdtd diff --git a/src/cpp/main_3d/engine_cpu.hpp b/src/cpp/main_3d/engine_cpu.hpp index e685caf..d7b7a62 100644 --- a/src/cpp/main_3d/engine_cpu.hpp +++ b/src/cpp/main_3d/engine_cpu.hpp @@ -12,18 +12,5 @@ namespace pffdtd { auto run(Simulation3D& sd) -> double; -double process_bnl_pts_fd( - Real* u0b, - Real const* u2b, - Real const* ssaf_bnl, - int8_t const* mat_bnl, - int64_t Nbl, - int8_t* Mb, - Real lo2, - Real* vh1, - Real* gh1, - MatQuad const* mat_quads, - Real const* mat_beta -); } // namespace pffdtd diff --git a/src/cpp/main_3d/engine_cuda.hpp b/src/cpp/main_3d/engine_cuda.hpp deleted file mode 100644 index c53ac45..0000000 --- a/src/cpp/main_3d/engine_cuda.hpp +++ /dev/null @@ -1,1230 +0,0 @@ -// SPDX-License-Identifier: MIT -// SPDX-FileCopyrightText: 2021 Brian Hamilton -// GPU-based implementation of FDTD engine (using CUDA). - -#ifndef _GPU_ENGINE_H -#define _GPU_ENGINE_H - -#include "fdtd_common.h" - -#include "pffdtd/utility.h" -#include "pffdtd/progress.hpp" - -#include -#include //for malloc -#include -#include //for assert -#include //for bool -#include //NAN - -#define CU_DIV_CEIL(x,y) ((DIV_CEIL(x,y)==0)? (1) : (DIV_CEIL(x,y))) //want 0 to map to 1, otherwise kernel errors - -#if !USING_CUDA -#error -#endif - -//thread-block dims for 3d kernels -#define cuBx 32 -#define cuBy 2 -#define cuBz 2 - -//thread-block dims for 2d kernels (fcc fold, ABCs) -#define cuBx2 16 -#define cuBy2 8 - -//thread-block dims for 1d kernels (bn, ABC loss) -#define cuBrw 128 -#define cuBb 128 - -//constant memory (all per device) -__constant__ Real c1; -__constant__ Real c2; -__constant__ Real cl; -__constant__ Real csl2; -__constant__ Real clo2; -__constant__ int64_t cuNx; -__constant__ int64_t cuNy; -__constant__ int64_t cuNz; -__constant__ int64_t cuNb; -__constant__ int64_t cuNbl; -__constant__ int64_t cuNba; -__constant__ int64_t cuNxNy; -__constant__ int8_t cuMb[MNm]; //to store Mb per mat (MNm has to be hash-defined) - -uint64_t print_gpu_details(int i); -void check_sorted( Simulation3D const& sd); -void split_data( Simulation3D const& sd, struct gpuHostData *ghds, int ngpus); -double run(Simulation3D &sd); -//CUDA kernels -__global__ void KernelAirCart(Real * __restrict__ u0, const Real * __restrict__ u1, const uint8_t * __restrict__ bn_mask); -__global__ void KernelAirFCC(Real * __restrict__ u0, const Real * __restrict__ u1, const uint8_t * __restrict__ bn_mask); -__global__ void KernelFoldFCC(Real * __restrict__ u1); -__global__ void KernelBoundaryRigidCart(Real * __restrict__ u0, const Real * __restrict__ u1, - const uint16_t * __restrict__ adj_bn, - const int64_t * __restrict__ bn_ixyz, - const int8_t * __restrict__ K_bn); -__global__ void KernelBoundaryRigidFCC(Real * __restrict__ u0, const Real * __restrict__ u1, - const uint16_t * __restrict__ adj_bn, - const int64_t * __restrict__ bn_ixyz, - const int8_t * __restrict__ K_bn); -__global__ void KernelBoundaryABC(Real * __restrict__ u0, - const Real * __restrict__ u2ba, - const int8_t * __restrict__ Q_bna, - const int64_t * __restrict__ bna_ixyz); -__global__ void KernelBoundaryFD(Real * __restrict__ u0b, const Real *u2b, - Real * __restrict__ vh1, Real * __restrict__ gh1, - const Real * ssaf_bnl, const int8_t * mat_bnl, - const Real * __restrict__ mat_beta, const struct MatQuad * __restrict__ mat_quads); -__global__ void AddIn(Real *u0, Real sample); -__global__ void CopyToGridKernel(Real *u, const Real *buffer, const int64_t *locs, int64_t N); -__global__ void CopyFromGridKernel(Real *buffer, const Real *u, const int64_t *locs, int64_t N); -__global__ void FlipHaloXY_Zbeg(Real * __restrict__ u1); -__global__ void FlipHaloXY_Zend(Real * __restrict__ u1); -__global__ void FlipHaloXZ_Ybeg(Real * __restrict__ u1); -__global__ void FlipHaloXZ_Yend(Real * __restrict__ u1); -__global__ void FlipHaloYZ_Xbeg(Real * __restrict__ u1); -__global__ void FlipHaloYZ_Xend(Real * __restrict__ u1); - -//this is data on host, sometimes copied and recomputed for copy to GPU devices (indices), sometimes just aliased pointers (scalar arrays) -struct gpuHostData { //arrays on host (for copy), mirrors gpu local data - double *in_sigs; //aliased - Real *u_out_buf; //aliased - double *u_out; //aliased - Real *ssaf_bnl; //aliased - int64_t *in_ixyz; //recomputed - int64_t *out_ixyz; //recomputed - int64_t *bn_ixyz; //recomputed - int64_t *bnl_ixyz; //recomputed - int64_t *bna_ixyz; //recomputed - int8_t *Q_bna; //aliased - uint16_t *adj_bn; //aliased - int8_t *mat_bnl; //aliased - uint8_t *bn_mask; //recomputed - int8_t *K_bn; //aliased - int64_t Ns; - int64_t Nr; - int64_t Npts; - int64_t Nx; - int64_t Nxh; - int64_t Nb; - int64_t Nbl; - int64_t Nba; - int64_t Nbm; //bytes for bn_mask -}; - -//these are arrays pointing to GPU device memory, or CUDA stuff (dim3, events) -struct gpuData { //for or on gpu (arrays all on GPU) - int64_t *bn_ixyz; - int64_t *bnl_ixyz; - int64_t *bna_ixyz; - int8_t *Q_bna; - int64_t *out_ixyz; - uint16_t *adj_bn; - Real *ssaf_bnl; - uint8_t *bn_mask; - int8_t *mat_bnl; - int8_t *K_bn; - Real *mat_beta; - struct MatQuad *mat_quads; - Real *u0; - Real *u1; - Real *u0b; - Real *u1b; - Real *u2b; - Real *u2ba; - Real *vh1; - Real *gh1; - Real *u_out_buf; - dim3 block_dim_air; - dim3 grid_dim_air; - dim3 block_dim_fold; - dim3 grid_dim_fold; - dim3 block_dim_readout; - dim3 grid_dim_readout; - dim3 block_dim_bn; - dim3 block_dim_halo_xy; - dim3 block_dim_halo_yz; - dim3 block_dim_halo_xz; - dim3 grid_dim_bn; - dim3 grid_dim_bnl; - dim3 grid_dim_bna; - dim3 grid_dim_halo_xy; - dim3 grid_dim_halo_yz; - dim3 grid_dim_halo_xz; - cudaStream_t cuStream_air; - cudaStream_t cuStream_bn; - cudaEvent_t cuEv_air_start; - cudaEvent_t cuEv_air_end; - cudaEvent_t cuEv_bn_roundtrip_start; - cudaEvent_t cuEv_bn_roundtrip_end; - cudaEvent_t cuEv_readout_end; - int64_t totalmembytes; -}; - - -//standard error checking -#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); } -inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true) -{ - if (code != cudaSuccess) - { - fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); - if (abort) exit(code); - } -} - -//print some device details -uint64_t print_gpu_details(int i) { - cudaDeviceProp prop; - cudaGetDeviceProperties(&prop, i); - printf("\nDevice Number: %d [%s]\n", i, prop.name); - printf(" Compute: %d.%d\n",prop.major,prop.minor); - printf(" Peak Memory Bandwidth: %.3f GB/s\n", 2.0*prop.memoryClockRate*(prop.memoryBusWidth/8)/1.0e6); - printf(" Total global memory: [ %.3f GB | %.3f GiB | %lu MiB ]\n", (double)prop.totalGlobalMem/(1e9), (double)prop.totalGlobalMem/1073741824ULL, prop.totalGlobalMem>>20); - printf(" Registers per block: %d\n", prop.regsPerBlock); - printf(" Concurrent Kernels: %d\n", prop.concurrentKernels); - printf(" Async Engine: %d\n", prop.asyncEngineCount); - printf("\n"); - return prop.totalGlobalMem; -} - -//NB. 'x' is contiguous dim in CUDA domain - -//vanilla scheme, unrolled, intrinsics to control rounding errors -__global__ void KernelAirCart(Real * __restrict__ u0, const Real * __restrict__ u1, - const uint8_t * __restrict__ bn_mask) -{ - int64_t cx = blockIdx.x*cuBx + threadIdx.x + 1; - int64_t cy = blockIdx.y*cuBy + threadIdx.y + 1; - int64_t cz = blockIdx.z*cuBz + threadIdx.z + 1; - if ((cx>3],ii%8)) ) { - u0[ii] = tmp1; - } - } -} - -//air update for FCC, on folded grid (improvement to 2013 DAFx paper) -__global__ void KernelAirFCC(Real * __restrict__ u0, const Real * __restrict__ u1, - const uint8_t * __restrict__ bn_mask) -{ - // get ix,iy,iz from thread and block Id's - int64_t cx = blockIdx.x*cuBx + threadIdx.x + 1; - int64_t cy = blockIdx.y*cuBy + threadIdx.y + 1; - int64_t cz = blockIdx.z*cuBz + threadIdx.z + 1; - if ((cx>3],ii%8)) ) { - u0[ii] = tmp1; - } - } -} - -//this folds in half of FCC subgrid so everything is nicely homogenous (no braching for stencil) -__global__ void KernelFoldFCC(Real * __restrict__ u1) -{ - int64_t cx = blockIdx.x*cuBx2 + threadIdx.x; - int64_t cz = blockIdx.y*cuBy2 + threadIdx.y; - //fold is along middle dimension - if ((cxbDh)*vh1int[m] - (tm->bFh)*gh1int[m] ); - } - - Real du = u0bint-u2bint; - - for (int8_t m=0; mb)*du + (tm->bd)*vh1int[m] - _2*(tm->bFh)*gh1int[m]; - gh1[nbm] = gh1int[m] + (vh0m + vh1int[m])/_2; - vh1[nbm] = vh0m; - } - u0b[nb] = u0bint; - } -} - -//add source input (one at a time for simplicity) -__global__ void AddIn(Real *u0, Real sample) -{ - u0[0] += sample; -} - -//dst-src copy from buffer to grid -__global__ void CopyToGridKernel(Real *u, const Real *buffer, const int64_t *locs, int64_t N) -{ - int64_t i = blockIdx.x*cuBrw + threadIdx.x; - if (i bn_ixyz[i-1]); //check save_gpu_folder - for (int64_t i=1; i bnl_ixyz[i-1]); - for (int64_t i=1; i bna_ixyz[i-1]); - for (int64_t i=1; i in_ixyz[i-1]); - for (int64_t i=1; i= out_ixyz[i-1]); //possible to have duplicates -} - -//counts for splitting data across GPUs -void split_data( Simulation3D const& sd, struct gpuHostData *ghds, int ngpus) { - int64_t Nx = sd.Nx; - int64_t Ny = sd.Ny; - int64_t Nz = sd.Nz; - struct gpuHostData *ghd; - //initialise - for (int gid=0; gidNx = 0; - ghd->Nb = 0; - ghd->Nbl = 0; - ghd->Nba = 0; - ghd->Ns = 0; - ghd->Nr = 0; - } - - //split Nx layers (Nz contiguous) - int64_t Nxm = Nx/ngpus; - int64_t Nxl = Nx % ngpus; - - for (int gid=0; gidNx = Nxm; - } - for (int gid=0; gidNx += 1; - } - int64_t Nx_check = 0; - for (int gid=0; gidNx,Nx); - Nx_check += ghd->Nx; - } - assert(Nx_check==Nx); - - //now count Nr,Ns,Nb for each device - int64_t Nxcc[ngpus]; - Nxcc[0] = ghds[0].Nx; - printf("Nxcc[%d]=%ld\n",0,Nxcc[0]); - for (int gid=1; gidNx + Nxcc[gid-1]; - printf("Nxcc[%d]=%ld\n",gid,Nxcc[gid]); - } - - //bn_ixyz - Nb - int64_t *bn_ixyz = sd.bn_ixyz; - int64_t Nb = sd.Nb; - { - int gid=0; - for (int64_t i=0; i= Nxcc[gid]*Ny*Nz) { - gid++; - } - (ghds[gid].Nb)++; - } - } - int64_t Nb_check = 0; - for (int gid=0; gidNb,Nb); - Nb_check += ghd->Nb; - } - assert(Nb_check==Nb); - - //bnl_ixyz - Nbl - int64_t *bnl_ixyz = sd.bnl_ixyz; - int64_t Nbl = sd.Nbl; - { - int gid=0; - for (int64_t i=0; i= Nxcc[gid]*Ny*Nz) { - gid++; - } - (ghds[gid].Nbl)++; - } - } - int64_t Nbl_check = 0; - for (int gid=0; gidNbl,Nbl); - Nbl_check += ghd->Nbl; - } - assert(Nbl_check==Nbl); - - //bna_ixyz - Nba - int64_t *bna_ixyz = sd.bna_ixyz; - int64_t Nba = sd.Nba; - { - int gid=0; - for (int64_t i=0; i= Nxcc[gid]*Ny*Nz) { - gid++; - } - (ghds[gid].Nba)++; - } - } - int64_t Nba_check = 0; - for (int gid=0; gidNba,Nba); - Nba_check += ghd->Nba; - } - assert(Nba_check==Nba); - - - //in_ixyz - Ns - int64_t *in_ixyz = sd.in_ixyz; - int64_t Ns = sd.Ns; - { - int gid=0; - for (int64_t i=0; i= Nxcc[gid]*Ny*Nz) { - gid++; - } - (ghds[gid].Ns)++; - } - } - int64_t Ns_check = 0; - for (int gid=0; gidNs,Ns); - Ns_check += ghd->Ns; - } - assert(Ns_check==Ns); - - //out_ixyz - Nr - int64_t *out_ixyz = sd.out_ixyz; - int64_t Nr = sd.Nr; - { - int gid=0; - for (int64_t i=0; i= Nxcc[gid]*Ny*Nz) { - gid++; - } - (ghds[gid].Nr)++; - } - } - int64_t Nr_check = 0; - for (int gid=0; gidNr,Nr); - Nr_check += ghd->Nr; - } - assert(Nr_check==Nr); -} - -//run the sim! -double run(Simulation3D &sd) -{ - //if you want to test synchronous, env variable for that - const char* s = getenv("CUDA_LAUNCH_BLOCKING"); - if (s != NULL) { - if (s[0]=='1') { - printf("******************SYNCHRONOUS (DEBUG ONLY!!!)*********************\n"); - printf("...continue?\n"); - getchar(); - } - } - - assert((sd.fcc_flag != 1)); //uses either cartesian or FCC folded grid - - int ngpus,max_ngpus; - cudaGetDeviceCount(&max_ngpus); //control outside with CUDA_VISIBLE_DEVICES - ngpus = max_ngpus; - assert(ngpus < (sd.Nx)); - struct gpuData *gds; - allocate_zeros((void **)&gds, ngpus*sizeof(gpuData)); - struct gpuHostData *ghds; - allocate_zeros((void **)&ghds, ngpus*sizeof(gpuHostData)); //one bit per - - if (ngpus>1) check_sorted(sd); //needs to be sorted for multi-GPU - - //get local counts for Nx,Nb,Nr,Ns - split_data(sd, ghds, ngpus); - - for (int gid=0; gid < ngpus; gid++) { - gds[gid].totalmembytes = print_gpu_details(gid); - } - - Real lo2 = sd.lo2; - Real a1 = sd.a1; - Real a2 = sd.a2; - Real l = sd.l; - Real sl2 = sd.sl2; - - //timing stuff - double time_elapsed=0.0; - double time_elapsed_bn=0.0; - double time_elapsed_sample; - double time_elapsed_sample_bn; - double time_elapsed_air=0.0; //feed into print/process - double time_elapsed_sample_air; //feed into print/process - float millis_since_start; - float millis_since_sample_start; - - printf("a1 = %.16g\n",a1); - printf("a2 = %.16g\n",a2); - - //start moving data to GPUs - for (int gid=0; gid < ngpus; gid++) { - struct gpuHostData *ghd = &(ghds[gid]); - printf("GPU %d -- ",gid); - printf("Nx=%ld Ns=%ld Nr=%ld Nb=%ld Nbl=%ld Nba=%ld\n",ghd->Nx,ghd->Ns,ghd->Nr,ghd->Nb,ghd->Nbl,ghd->Nba); - } - - int64_t Ns_read=0; - int64_t Nr_read=0; - int64_t Nb_read=0; - int64_t Nbl_read=0; - int64_t Nba_read=0; - int64_t Nx_read=0; - int64_t Nx_pos=0; - //uint64_t Nx_pos2=0; - - Real *u_out_buf; - gpuErrchk( cudaMallocHost(&u_out_buf, (size_t)(sd.Nr*sizeof(Real))) ); - memset(u_out_buf, 0, (size_t)(sd.Nr*sizeof(Real))); //set floats to zero - - int64_t Nzy = (sd.Nz)*(sd.Ny); //area-slice - - //here we recalculate indices to move to devices - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - - struct gpuData *gd = &(gds[gid]); - struct gpuHostData *ghd = &(ghds[gid]); - printf("---------\n"); - printf("GPU %d\n",gid); - printf("---------\n"); - - printf("Nx to read = %ld\n",ghd->Nx); - printf("Nb to read = %ld\n",ghd->Nb); - printf("Nbl to read = %ld\n",ghd->Nbl); - printf("Nba to read = %ld\n",ghd->Nba); - printf("Ns to read = %ld\n",ghd->Ns); - printf("Nr to read = %ld\n",ghd->Nr); - - //Nxh (effective Nx with extra halos) - ghd->Nxh = ghd->Nx; - if (gid>0) (ghd->Nxh)++; //add bottom halo - if (gidNxh)++; //add top halo - //calculate Npts for this device - ghd->Npts = Nzy*(ghd->Nxh); - //boundary mask - ghd->Nbm = CU_DIV_CEIL(ghd->Npts,8); - - printf("Nx=%ld Ns=%ld Nr=%ld Nb=%ld, Npts=%ld\n",ghd->Nx,ghd->Ns,ghd->Nr,ghd->Nb,ghd->Npts); - - //aliased pointers (to memory already allocated) - ghd->in_sigs = sd.in_sigs + Ns_read*sd.Nt; - ghd->ssaf_bnl = sd.ssaf_bnl + Nbl_read; - ghd->adj_bn = sd.adj_bn + Nb_read; - ghd->mat_bnl = sd.mat_bnl + Nbl_read; - ghd->K_bn = sd.K_bn + Nb_read; - ghd->Q_bna = sd.Q_bna + Nba_read; - ghd->u_out = sd.u_out + Nr_read*sd.Nt; - ghd->u_out_buf = u_out_buf + Nr_read; - - //recalculate indices, these are associated host versions to copy over to devices - allocate_zeros((void **)&(ghd->bn_ixyz), ghd->Nb*sizeof(int64_t)); - allocate_zeros((void **)&(ghd->bnl_ixyz), ghd->Nbl*sizeof(int64_t)); - allocate_zeros((void **)&(ghd->bna_ixyz), ghd->Nba*sizeof(int64_t)); - allocate_zeros((void **)&(ghd->bn_mask), ghd->Nbm*sizeof(uint8_t)); - allocate_zeros((void **)&(ghd->in_ixyz), ghd->Ns*sizeof(int64_t)); - allocate_zeros((void **)&(ghd->out_ixyz), ghd->Nr*sizeof(int64_t)); - - int64_t offset = Nzy*Nx_pos; - for (int64_t nb=0; nb<(ghd->Nb); nb++) { - int64_t ii = sd.bn_ixyz[nb+Nb_read]; //global index - int64_t jj = ii - offset; //local index - assert(jj>=0); - assert(jj < ghd->Npts); - ghd->bn_ixyz[nb] = jj; - SET_BIT_VAL(ghd->bn_mask[jj>>3],jj%8,GET_BIT(sd.bn_mask[ii>>3],ii%8)); //set bit - } - for (int64_t nb=0; nb<(ghd->Nbl); nb++) { - int64_t ii = sd.bnl_ixyz[nb+Nbl_read]; //global index - int64_t jj = ii - offset; //local index - assert(jj>=0); - assert(jj < ghd->Npts); - ghd->bnl_ixyz[nb] = jj; - } - - for (int64_t nb=0; nb<(ghd->Nba); nb++) { - int64_t ii = sd.bna_ixyz[nb+Nba_read]; //global index - int64_t jj = ii - offset; //local index - assert(jj>=0); - assert(jj < ghd->Npts); - ghd->bna_ixyz[nb] = jj; - } - - - for (int64_t ns=0; ns<(ghd->Ns); ns++) { - int64_t ii = sd.in_ixyz[ns+Ns_read]; - int64_t jj = ii - offset; - assert(jj>=0); - assert(jj < ghd->Npts); - ghd->in_ixyz[ns] = jj; - } - for (int64_t nr=0; nr<(ghd->Nr); nr++) { - int64_t ii = sd.out_ixyz[nr+Nr_read]; - int64_t jj = ii - offset; - assert(jj>=0); - assert(jj < ghd->Npts); - ghd->out_ixyz[nr] = jj; - } - - gpuErrchk( cudaMalloc(&(gd->u0), (size_t)((ghd->Npts)*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u0, 0, (size_t)((ghd->Npts)*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->u1), (size_t)((ghd->Npts)*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u1, 0, (size_t)((ghd->Npts)*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->K_bn), (size_t)(ghd->Nb*sizeof(int8_t))) ); - gpuErrchk( cudaMemcpy(gd->K_bn, ghd->K_bn, ghd->Nb*sizeof(int8_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->ssaf_bnl), (size_t)(ghd->Nbl*sizeof(Real))) ); - gpuErrchk( cudaMemcpy(gd->ssaf_bnl, ghd->ssaf_bnl, ghd->Nbl*sizeof(Real), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->u0b), (size_t)(ghd->Nbl*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u0b, 0, (size_t)(ghd->Nbl*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->u1b), (size_t)(ghd->Nbl*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u1b, 0, (size_t)(ghd->Nbl*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->u2b), (size_t)(ghd->Nbl*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u2b, 0, (size_t)(ghd->Nbl*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->u2ba), (size_t)(ghd->Nba*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u2ba, 0, (size_t)(ghd->Nba*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->vh1), (size_t)(ghd->Nbl*MMb*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->vh1, 0, (size_t)(ghd->Nbl*MMb*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->gh1), (size_t)(ghd->Nbl*MMb*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->gh1, 0, (size_t)(ghd->Nbl*MMb*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->u_out_buf), (size_t)(ghd->Nr*sizeof(Real))) ); - gpuErrchk( cudaMemset(gd->u_out_buf, 0, (size_t)(ghd->Nr*sizeof(Real))) ); - - gpuErrchk( cudaMalloc(&(gd->bn_ixyz), (size_t)(ghd->Nb*sizeof(int64_t))) ); - gpuErrchk( cudaMemcpy(gd->bn_ixyz, ghd->bn_ixyz, (size_t)ghd->Nb*sizeof(int64_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->bnl_ixyz), (size_t)(ghd->Nbl*sizeof(int64_t))) ); - gpuErrchk( cudaMemcpy(gd->bnl_ixyz, ghd->bnl_ixyz, (size_t)ghd->Nbl*sizeof(int64_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->bna_ixyz), (size_t)(ghd->Nba*sizeof(int64_t))) ); - gpuErrchk( cudaMemcpy(gd->bna_ixyz, ghd->bna_ixyz, (size_t)ghd->Nba*sizeof(int64_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->Q_bna), (size_t)(ghd->Nba*sizeof(int8_t))) ); - gpuErrchk( cudaMemcpy(gd->Q_bna, ghd->Q_bna, ghd->Nba*sizeof(int8_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->out_ixyz), (size_t)(ghd->Nr*sizeof(int64_t))) ); - gpuErrchk( cudaMemcpy(gd->out_ixyz, ghd->out_ixyz, (size_t)ghd->Nr*sizeof(int64_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->adj_bn), (size_t)(ghd->Nb*sizeof(uint16_t))) ); - gpuErrchk( cudaMemcpy(gd->adj_bn, ghd->adj_bn, (size_t)ghd->Nb*sizeof(uint16_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->mat_bnl), (size_t)(ghd->Nbl*sizeof(int8_t))) ); - gpuErrchk( cudaMemcpy(gd->mat_bnl, ghd->mat_bnl, (size_t)ghd->Nbl*sizeof(int8_t), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->mat_beta), (size_t)sd.Nm*sizeof(Real)) ); - gpuErrchk( cudaMemcpy(gd->mat_beta, sd.mat_beta, (size_t)sd.Nm*sizeof(Real), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->mat_quads), (size_t)sd.Nm*MMb*sizeof(MatQuad)) ); - gpuErrchk( cudaMemcpy(gd->mat_quads, sd.mat_quads, (size_t)sd.Nm*MMb*sizeof(MatQuad), cudaMemcpyHostToDevice) ); - - gpuErrchk( cudaMalloc(&(gd->bn_mask), (size_t)(ghd->Nbm*sizeof(uint8_t))) ); - gpuErrchk( cudaMemcpy(gd->bn_mask, ghd->bn_mask, (size_t)ghd->Nbm*sizeof(uint8_t), cudaMemcpyHostToDevice) ); - - Ns_read += ghd->Ns; - Nr_read += ghd->Nr; - Nb_read += ghd->Nb; - Nbl_read += ghd->Nbl; - Nba_read += ghd->Nba; - Nx_read += ghd->Nx; - Nx_pos = Nx_read-1; //back up one at subsequent stage - - printf("Nx_read = %ld\n",Nx_read); - printf("Nb_read = %ld\n",Nb_read); - printf("Nbl_read = %ld\n",Nbl_read); - printf("Ns_read = %ld\n",Ns_read); - printf("Nr_read = %ld\n",Nr_read); - - printf("Global memory allocation done\n"); - printf("\n"); - - //swapping x and z here (CUDA has first dim contiguous) - gpuErrchk( cudaMemcpyToSymbol(cuNx,&(sd.Nz),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNy,&(sd.Ny),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNz,&(ghd->Nxh),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNb,&(ghd->Nb),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNbl,&(ghd->Nbl),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNba,&(ghd->Nba),sizeof(int64_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuMb,sd.Mb,sd.Nm*sizeof(int8_t)) ); - gpuErrchk( cudaMemcpyToSymbol(cuNxNy,&Nzy,sizeof(int64_t)) ); //same for all devices - - gpuErrchk( cudaMemcpyToSymbol(c1,&a1,sizeof(Real)) ); - gpuErrchk( cudaMemcpyToSymbol(c2,&a2,sizeof(Real)) ); - gpuErrchk( cudaMemcpyToSymbol(cl,&l,sizeof(Real)) ); - gpuErrchk( cudaMemcpyToSymbol(csl2,&sl2,sizeof(Real)) ); - gpuErrchk( cudaMemcpyToSymbol(clo2,&lo2,sizeof(Real)) ); - - printf("Constant memory loaded\n"); - printf("\n"); - - //threads grids and blocks (swap x and z) - int64_t cuGx = CU_DIV_CEIL(sd.Nz-2,cuBx); - int64_t cuGy = CU_DIV_CEIL(sd.Ny-2,cuBy); - int64_t cuGz = CU_DIV_CEIL(ghd->Nxh-2,cuBz); - int64_t cuGr = CU_DIV_CEIL(ghd->Nr,cuBrw); - int64_t cuGb = CU_DIV_CEIL(ghd->Nb,cuBb); - int64_t cuGbl = CU_DIV_CEIL(ghd->Nbl,cuBb); - int64_t cuGba = CU_DIV_CEIL(ghd->Nba,cuBb); - - int64_t cuGx2 = CU_DIV_CEIL(sd.Nz,cuBx2); //full face - int64_t cuGz2 = CU_DIV_CEIL(ghd->Nxh,cuBy2); //full face - - assert(cuGx >= 1); - assert(cuGy >= 1); - assert(cuGz >= 1); - assert(cuGr >= 1); - assert(cuGb >= 1); - assert(cuGbl >= 1); - assert(cuGba >= 1); - - gd->block_dim_air = dim3(cuBx, cuBy, cuBz); - gd->block_dim_readout = dim3(cuBrw, 1, 1); - gd->block_dim_bn = dim3(cuBb, 1, 1); - - gd->grid_dim_air = dim3(cuGx, cuGy, cuGz); - gd->grid_dim_readout = dim3(cuGr, 1, 1); - gd->grid_dim_bn = dim3(cuGb, 1, 1); - gd->grid_dim_bnl = dim3(cuGbl, 1, 1); - gd->grid_dim_bna = dim3(cuGba, 1, 1); - - gd->block_dim_halo_xy = dim3(cuBx2, cuBy2, 1); - gd->block_dim_halo_yz = dim3(cuBx2, cuBy2, 1); - gd->block_dim_halo_xz = dim3(cuBx2, cuBy2, 1); - gd->grid_dim_halo_xy = dim3(CU_DIV_CEIL(sd.Nz,cuBx2), CU_DIV_CEIL(sd.Ny,cuBy2), 1); - gd->grid_dim_halo_yz = dim3(CU_DIV_CEIL(sd.Ny,cuBx2), CU_DIV_CEIL(ghd->Nxh,cuBy2), 1); - gd->grid_dim_halo_xz = dim3(CU_DIV_CEIL(sd.Nz,cuBx2), CU_DIV_CEIL(ghd->Nxh,cuBy2), 1); - - gd->block_dim_fold = dim3(cuBx2,cuBy2,1); - gd->grid_dim_fold = dim3(cuGx2,cuGz2,1); - - //create streams - gpuErrchk( cudaStreamCreate(&(gd->cuStream_air)) ); - gpuErrchk( cudaStreamCreate(&(gd->cuStream_bn)) ); //no priority - - //cuda events - gpuErrchk( cudaEventCreate(&(gd->cuEv_air_start)) ); - gpuErrchk( cudaEventCreate(&(gd->cuEv_air_end)) ); - gpuErrchk( cudaEventCreate(&(gd->cuEv_bn_roundtrip_start)) ); - gpuErrchk( cudaEventCreate(&(gd->cuEv_bn_roundtrip_end)) ); - gpuErrchk( cudaEventCreate(&(gd->cuEv_readout_end)) ); - } - assert(Nb_read == sd.Nb); - assert(Nbl_read == sd.Nbl); - assert(Nba_read == sd.Nba); - assert(Nr_read == sd.Nr); - assert(Ns_read == sd.Ns); - assert(Nx_read == sd.Nx); - - //these will be on first GPU only - cudaEvent_t cuEv_main_start; - cudaEvent_t cuEv_main_end; - cudaEvent_t cuEv_main_sample_start; - cudaEvent_t cuEv_main_sample_end; - gpuErrchk( cudaSetDevice(0) ); - gpuErrchk( cudaEventCreate(&cuEv_main_start) ); - gpuErrchk( cudaEventCreate(&cuEv_main_end) ); - gpuErrchk( cudaEventCreate(&cuEv_main_sample_start) ); - gpuErrchk( cudaEventCreate(&cuEv_main_sample_end) ); - - for (int64_t n=0; ncuEv_bn_roundtrip_start,gd->cuStream_bn) ); - - //boundary updates - if (sd.fcc_flag==0) { - KernelBoundaryRigidCart<<grid_dim_bn,gd->block_dim_bn,0,gd->cuStream_bn>>>(gd->u0,gd->u1,gd->adj_bn,gd->bn_ixyz,gd->K_bn); - } - else { - KernelFoldFCC<<grid_dim_fold,gd->block_dim_fold,0,gd->cuStream_bn>>>(gd->u1); - KernelBoundaryRigidFCC<<grid_dim_bn,gd->block_dim_bn,0,gd->cuStream_bn>>>(gd->u0,gd->u1,gd->adj_bn,gd->bn_ixyz,gd->K_bn); - } - //using buffer to then update FD boundaries - CopyFromGridKernel<<grid_dim_bnl,gd->block_dim_bn,0,gd->cuStream_bn>>>(gd->u0b, gd->u0, gd->bnl_ixyz, ghd->Nbl); - //possible this could be moved to host - KernelBoundaryFD<<grid_dim_bnl,gd->block_dim_bn,0,gd->cuStream_bn>>>(gd->u0b,gd->u2b,gd->vh1,gd->gh1,gd->ssaf_bnl,gd->mat_bnl,gd->mat_beta,gd->mat_quads); - //copy to back to grid - CopyToGridKernel<<grid_dim_bnl,gd->block_dim_bn,0,gd->cuStream_bn>>>(gd->u0, gd->u0b, gd->bnl_ixyz, ghd->Nbl); - gpuErrchk( cudaEventRecord(gd->cuEv_bn_roundtrip_end,gd->cuStream_bn) ); - - //air updates (including source - gpuErrchk( cudaStreamWaitEvent(gd->cuStream_air,gd->cuEv_bn_roundtrip_end,0) ); //might as well wait - //run air kernel (with mask) - gpuErrchk( cudaEventRecord(gd->cuEv_air_start,gd->cuStream_air) ); - - //for absorbing boundaries at boundaries of grid - CopyFromGridKernel<<grid_dim_bna,gd->block_dim_bn,0,gd->cuStream_air>>>(gd->u2ba, gd->u0, gd->bna_ixyz, ghd->Nba); - if (gid==0) { - FlipHaloXY_Zbeg<<grid_dim_halo_xy,gd->block_dim_halo_xy,0,gd->cuStream_air>>>(gd->u1); - } - if (gid==ngpus-1) { - FlipHaloXY_Zend<<grid_dim_halo_xy,gd->block_dim_halo_xy,0,gd->cuStream_air>>>(gd->u1); - } - FlipHaloXZ_Ybeg<<grid_dim_halo_xz,gd->block_dim_halo_xz,0,gd->cuStream_air>>>(gd->u1); - if (sd.fcc_flag==0) { - FlipHaloXZ_Yend<<grid_dim_halo_xz,gd->block_dim_halo_xz,0,gd->cuStream_air>>>(gd->u1); - } - FlipHaloYZ_Xbeg<<grid_dim_halo_yz,gd->block_dim_halo_yz,0,gd->cuStream_air>>>(gd->u1); - FlipHaloYZ_Xend<<grid_dim_halo_yz,gd->block_dim_halo_yz,0,gd->cuStream_air>>>(gd->u1); - - //injecting source first, negating sample to add it in first (NB source on different stream than bn) - for (int64_t ns=0; nsNs; ns++) { - AddIn<<<1,1,0,gd->cuStream_air>>>(gd->u0 + ghd->in_ixyz[ns],(Real)(-(ghd->in_sigs[ns*sd.Nt+n]))); - } - //now air updates (not conflicting with bn updates because of bn_mask) - if (sd.fcc_flag==0) { - KernelAirCart<<grid_dim_air,gd->block_dim_air,0,gd->cuStream_air>>>(gd->u0,gd->u1,gd->bn_mask); - } - else { - KernelAirFCC<<grid_dim_air,gd->block_dim_air,0,gd->cuStream_air>>>(gd->u0,gd->u1,gd->bn_mask); - } - //boundary ABC loss - KernelBoundaryABC<<grid_dim_bna,gd->block_dim_bn,0,gd->cuStream_air>>>(gd->u0,gd->u2ba,gd->Q_bna,gd->bna_ixyz); - gpuErrchk( cudaEventRecord(gd->cuEv_air_end,gd->cuStream_air) ); //for timing - - //readouts - CopyFromGridKernel<<grid_dim_readout,gd->block_dim_readout,0,gd->cuStream_bn>>>(gd->u_out_buf, gd->u1, gd->out_ixyz, ghd->Nr); - //then async memory copy of outputs (not really async because on same stream as CopyFromGridKernel) - gpuErrchk( cudaMemcpyAsync(ghd->u_out_buf, gd->u_out_buf, ghd->Nr*sizeof(Real), cudaMemcpyDeviceToHost, gd->cuStream_bn) ); - gpuErrchk( cudaEventRecord(gd->cuEv_readout_end,gd->cuStream_bn) ); - } - - //readouts - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - struct gpuData *gd = &(gds[gid]); - struct gpuHostData *ghd = &(ghds[gid]); - gpuErrchk( cudaEventSynchronize(gd->cuEv_readout_end) ); - //copy grid points off output buffer - for (int64_t nr=0; nrNr; nr++) { - ghd->u_out[nr*sd.Nt + n] = (double)(ghd->u_out_buf[nr]); - } - } - //synchronise streams - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - struct gpuData *gd = &(gds[gid]); //don't really need to set gpu device to sync - gpuErrchk( cudaStreamSynchronize(gd->cuStream_air) ); //interior complete - gpuErrchk( cudaStreamSynchronize(gd->cuStream_bn) ); //transfer complete - - } - //dst then src, stream with src gives best performance (CUDA thing) - - //now asynchronous halo swaps, even/odd pairs concurrent - //these are not async to rest of scheme, just async to other swaps - - //copy forward (even) - for (int gid=0; gid < ngpus-1; gid+=2) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaMemcpyPeerAsync(gds[gid+1].u0, gid+1, - gds[gid].u0 + Nzy*(ghds[gid].Nxh-2), gid, - (size_t)(Nzy*sizeof(Real)), - gds[gid].cuStream_bn) ); - } - //copy back (odd) - for (int gid=1; gid < ngpus; gid+=2) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaMemcpyPeerAsync(gds[gid-1].u0 + Nzy*(ghds[gid-1].Nxh-1), gid-1, - gds[gid].u0 + Nzy, gid, - (size_t)(Nzy*sizeof(Real)), - gds[gid].cuStream_bn) ); - } - //copy forward (odd) - for (int gid=1; gid < ngpus-1; gid+=2) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaMemcpyPeerAsync(gds[gid+1].u0, gid+1, - gds[gid].u0 + Nzy*(ghds[gid].Nxh-2), gid, - (size_t)(Nzy*sizeof(Real)), - gds[gid].cuStream_bn) ); - } - //copy back (even) -- skip zero - for (int gid=2; gid < ngpus; gid+=2) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaMemcpyPeerAsync(gds[gid-1].u0 + Nzy*(ghds[gid-1].Nxh-1), gid-1, - gds[gid].u0 + Nzy, gid, - (size_t)(Nzy*sizeof(Real)), - gds[gid].cuStream_bn) ); - } - - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - struct gpuData *gd = &(gds[gid]); - gpuErrchk( cudaStreamSynchronize(gd->cuStream_bn) ); //transfer complete - } - for (int gid=0; gid < ngpus; gid++) { - struct gpuData *gd = &(gds[gid]); - //update pointers - Real *tmp_ptr; - tmp_ptr = gd->u1; - gd->u1 = gd->u0; - gd->u0 = tmp_ptr; - - //will use extra vector for this (simpler than extra copy kernel) - tmp_ptr = gd->u2b; - gd->u2b = gd->u1b; - gd->u1b = gd->u0b; - gd->u0b = tmp_ptr; - - if (gid==0) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaEventRecord(cuEv_main_sample_end,0) ); - } - } - - { - //timing only on gpu0 - gpuErrchk( cudaSetDevice(0) ); - struct gpuData *gd = &(gds[0]); - gpuErrchk( cudaEventSynchronize(cuEv_main_sample_end) ); //not sure this is correct - gpuErrchk( cudaEventElapsedTime(&millis_since_start, cuEv_main_start, cuEv_main_sample_end) ); - gpuErrchk( cudaEventElapsedTime(&millis_since_sample_start, cuEv_main_sample_start, cuEv_main_sample_end) ); - - time_elapsed = millis_since_start/1000; - time_elapsed_sample = millis_since_sample_start/1000; - - float millis_air, millis_bn; - gpuErrchk( cudaEventElapsedTime(&millis_air, gd->cuEv_air_start, gd->cuEv_air_end) ); - time_elapsed_sample_air = 0.001*millis_air; - time_elapsed_air += time_elapsed_sample_air; - - //not full picutre, only first gpu - gpuErrchk( cudaEventElapsedTime(&millis_bn, gd->cuEv_bn_roundtrip_start, gd->cuEv_bn_roundtrip_end) ); - - time_elapsed_sample_bn = millis_bn/1000.0; - time_elapsed_bn += time_elapsed_sample_bn; - - pffdtd::print_progress(n, sd.Nt, sd.Npts, sd.Nb, time_elapsed, time_elapsed_sample, time_elapsed_air, time_elapsed_sample_air, time_elapsed_bn, time_elapsed_sample_bn, ngpus); - } - } - printf("\n"); - - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaPeekAtLastError() ); - gpuErrchk( cudaDeviceSynchronize() ); - } - { - //timing (on device 0) - gpuErrchk( cudaSetDevice(0) ); - gpuErrchk( cudaEventRecord(cuEv_main_end) ); - gpuErrchk( cudaEventSynchronize(cuEv_main_end) ); - - gpuErrchk( cudaEventElapsedTime(&millis_since_start, cuEv_main_start, cuEv_main_end) ); - time_elapsed = millis_since_start/1000; - } - - /*------------------------ - * FREE WILLY - ------------------------*/ - gpuErrchk( cudaSetDevice(0) ); - gpuErrchk( cudaEventDestroy(cuEv_main_start) ); - gpuErrchk( cudaEventDestroy(cuEv_main_end) ); - gpuErrchk( cudaEventDestroy(cuEv_main_sample_start) ); - gpuErrchk( cudaEventDestroy(cuEv_main_sample_end) ); - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - struct gpuData *gd = &(gds[gid]); - struct gpuHostData *ghd = &(ghds[gid]); - //cleanup streams - gpuErrchk( cudaStreamDestroy(gd->cuStream_air) ); - gpuErrchk( cudaStreamDestroy(gd->cuStream_bn) ); - - //cleanup events - gpuErrchk( cudaEventDestroy(gd->cuEv_air_start) ); - gpuErrchk( cudaEventDestroy(gd->cuEv_air_end) ); - gpuErrchk( cudaEventDestroy(gd->cuEv_bn_roundtrip_start) ); - gpuErrchk( cudaEventDestroy(gd->cuEv_bn_roundtrip_end) ); - gpuErrchk( cudaEventDestroy(gd->cuEv_readout_end) ); - - //free memory - gpuErrchk( cudaFree(gd->u0) ); - gpuErrchk( cudaFree(gd->u1) ); - gpuErrchk( cudaFree(gd->out_ixyz) ); - gpuErrchk( cudaFree(gd->bn_ixyz) ); - gpuErrchk( cudaFree(gd->bnl_ixyz) ); - gpuErrchk( cudaFree(gd->bna_ixyz) ); - gpuErrchk( cudaFree(gd->Q_bna) ); - gpuErrchk( cudaFree(gd->adj_bn) ); - gpuErrchk( cudaFree(gd->mat_bnl) ); - gpuErrchk( cudaFree(gd->K_bn) ); - gpuErrchk( cudaFree(gd->ssaf_bnl) ); - gpuErrchk( cudaFree(gd->mat_beta) ); - gpuErrchk( cudaFree(gd->mat_quads) ); - gpuErrchk( cudaFree(gd->bn_mask) ); - gpuErrchk( cudaFree(gd->u0b) ); - gpuErrchk( cudaFree(gd->u1b) ); - gpuErrchk( cudaFree(gd->u2b) ); - gpuErrchk( cudaFree(gd->u2ba) ); - gpuErrchk( cudaFree(gd->vh1) ); - gpuErrchk( cudaFree(gd->gh1) ); - gpuErrchk( cudaFree(gd->u_out_buf) ); - free(ghd->bn_mask); - free(ghd->bn_ixyz); - free(ghd->bnl_ixyz); - free(ghd->bna_ixyz); - free(ghd->in_ixyz); - free(ghd->out_ixyz); - } - gpuErrchk( cudaFreeHost(u_out_buf) ); - free(gds); - free(ghds); - - //reset after frees (for some reason it conflicts with cudaFreeHost) - for (int gid=0; gid < ngpus; gid++) { - gpuErrchk( cudaSetDevice(gid) ); - gpuErrchk( cudaDeviceReset() ); - } - - printf("Boundary loop: %.6fs, %.2f Mvox/s\n",time_elapsed_bn,sd.Nb*sd.Nt/1e6/time_elapsed_bn); - printf("Air update: %.6fs, %.2f Mvox/s\n",time_elapsed_air,sd.Npts*sd.Nt/1e6/time_elapsed_air); - printf("Combined (total): %.6fs, %.2f Mvox/s\n",time_elapsed,sd.Npts*sd.Nt/1e6/time_elapsed); - return time_elapsed; -} -#endif diff --git a/src/cpp/main_3d/engine_gpu.cu b/src/cpp/main_3d/engine_gpu.cu new file mode 100644 index 0000000..49ce8dc --- /dev/null +++ b/src/cpp/main_3d/engine_gpu.cu @@ -0,0 +1,1344 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2021 Brian Hamilton + +#include "engine_gpu.hpp" + +#include "pffdtd/assert.hpp" +#include "pffdtd/config.hpp" +#include "pffdtd/progress.hpp" +#include "pffdtd/utility.hpp" + +#include +#include +#include +#include +#include +#include + +namespace pffdtd { + +// want 0 to map to 1, otherwise kernel errors +constexpr auto CU_DIV_CEIL(auto x, auto y) { return ((DIV_CEIL(x, y) == 0) ? (1) : (DIV_CEIL(x, y))); } + +// thread-block dims for 3d kernels +constexpr auto cuBx = 32; +constexpr auto cuBy = 2; +constexpr auto cuBz = 2; + +// thread-block dims for 2d kernels (fcc fold, ABCs) +constexpr auto cuBx2 = 16; +constexpr auto cuBy2 = 8; + +// thread-block dims for 1d kernels (bn, ABC loss) +constexpr auto cuBrw = 128; +constexpr auto cuBb = 128; + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +// constant memory (all per device) +__constant__ Real c1; +__constant__ Real c2; +__constant__ Real cl; +__constant__ Real csl2; +__constant__ Real clo2; +__constant__ int64_t cuNx; +__constant__ int64_t cuNy; +__constant__ int64_t cuNz; +__constant__ int64_t cuNb; +__constant__ int64_t cuNbl; +__constant__ int64_t cuNba; +__constant__ int64_t cuNxNy; +__constant__ int8_t cuMb[MNm]; // to store Mb per mat + +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +// this is data on host, sometimes copied and recomputed for copy to GPU devices +// (indices), sometimes just aliased pointers (scalar arrays) +template +struct HostData { // arrays on host (for copy), mirrors gpu local data + double* in_sigs{}; // aliased + Float* u_out_buf{}; // aliased + double* u_out{}; // aliased + Float* ssaf_bnl{}; // aliased + int64_t* in_ixyz{}; // recomputed + int64_t* out_ixyz{}; // recomputed + int64_t* bn_ixyz{}; // recomputed + int64_t* bnl_ixyz{}; // recomputed + int64_t* bna_ixyz{}; // recomputed + int8_t* Q_bna{}; // aliased + uint16_t* adj_bn{}; // aliased + int8_t* mat_bnl{}; // aliased + uint8_t* bn_mask{}; // recomputed + int8_t* K_bn{}; // aliased + int64_t Ns{}; + int64_t Nr{}; + int64_t Npts{}; + int64_t Nx{}; + int64_t Nxh{}; + int64_t Nb{}; + int64_t Nbl{}; + int64_t Nba{}; + int64_t Nbm{}; // bytes for bn_mask +}; + +// these are arrays pointing to GPU device memory, or CUDA stuff (dim3, events) +template +struct DeviceData { // for or on gpu (arrays all on GPU) + int64_t* bn_ixyz{}; + int64_t* bnl_ixyz{}; + int64_t* bna_ixyz{}; + int8_t* Q_bna{}; + int64_t* out_ixyz{}; + uint16_t* adj_bn{}; + Float* ssaf_bnl{}; + uint8_t* bn_mask{}; + int8_t* mat_bnl{}; + int8_t* K_bn{}; + Float* mat_beta{}; + MatQuad* mat_quads{}; + Float* u0{}; + Float* u1{}; + Float* u0b{}; + Float* u1b{}; + Float* u2b{}; + Float* u2ba{}; + Float* vh1{}; + Float* gh1{}; + Float* u_out_buf{}; + dim3 block_dim_air; + dim3 grid_dim_air; + dim3 block_dim_fold; + dim3 grid_dim_fold; + dim3 block_dim_readout; + dim3 grid_dim_readout; + dim3 block_dim_bn; + dim3 block_dim_halo_xy; + dim3 block_dim_halo_yz; + dim3 block_dim_halo_xz; + dim3 grid_dim_bn; + dim3 grid_dim_bnl; + dim3 grid_dim_bna; + dim3 grid_dim_halo_xy; + dim3 grid_dim_halo_yz; + dim3 grid_dim_halo_xz; + cudaStream_t cuStream_air{}; + cudaStream_t cuStream_bn{}; + cudaEvent_t cuEv_air_start{}; + cudaEvent_t cuEv_air_end{}; + cudaEvent_t cuEv_bn_roundtrip_start{}; + cudaEvent_t cuEv_bn_roundtrip_end{}; + cudaEvent_t cuEv_readout_end{}; + int64_t totalmembytes{}; +}; + +// NB. 'x' is contiguous dim in CUDA domain + +// vanilla scheme, unrolled, intrinsics to control rounding errors +template +__global__ void +KernelAirCart(Float* __restrict__ u0, Float const* __restrict__ u1, uint8_t const* __restrict__ bn_mask) { + int64_t const cx = blockIdx.x * cuBx + threadIdx.x + 1; + int64_t const cy = blockIdx.y * cuBy + threadIdx.y + 1; + int64_t const cz = blockIdx.z * cuBz + threadIdx.z + 1; + if ((cx < cuNx - 1) && (cy < cuNy - 1) && (cz < cuNz - 1)) { + int64_t const ii = cz * cuNxNy + cy * cuNx + cx; + // divide-conquer add for better accuracy + Float tmp1 = NAN; + Float tmp2 = NAN; + tmp1 = ADD_O(u1[ii + cuNxNy], u1[ii - cuNxNy]); + tmp2 = ADD_O(u1[ii + cuNx], u1[ii - cuNx]); + tmp1 = ADD_O(tmp1, tmp2); + tmp2 = ADD_O(u1[ii + 1], u1[ii - 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp1 = FMA_D(c1, u1[ii], FMA_D(c2, tmp1, -u0[ii])); + + // write final value back to global memory + if ((GET_BIT(bn_mask[ii >> 3], ii % 8)) == 0) { + u0[ii] = tmp1; + } + } +} + +// air update for FCC, on folded grid (improvement to 2013 DAFx paper) +template +__global__ void +KernelAirFCC(Float* __restrict__ u0, Float const* __restrict__ u1, uint8_t const* __restrict__ bn_mask) { + // get ix,iy,iz from thread and block Id's + int64_t const cx = blockIdx.x * cuBx + threadIdx.x + 1; + int64_t const cy = blockIdx.y * cuBy + threadIdx.y + 1; + int64_t const cz = blockIdx.z * cuBz + threadIdx.z + 1; + if ((cx < cuNx - 1) && (cy < cuNy - 1) && (cz < cuNz - 1)) { + // x is contiguous + int64_t const ii = cz * cuNxNy + cy * cuNx + cx; + Float tmp1 = NAN; + Float tmp2 = NAN; + Float tmp3 = NAN; + Float tmp4 = NAN; + // divide-conquer add as much as possible + tmp1 = ADD_O(u1[ii + cuNxNy + cuNx], u1[ii - cuNxNy - cuNx]); + tmp2 = ADD_O(u1[ii + cuNx + 1], u1[ii - cuNx - 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp3 = ADD_O(u1[ii + cuNxNy + 1], u1[ii - cuNxNy - 1]); + tmp4 = ADD_O(u1[ii + cuNxNy - cuNx], u1[ii - cuNxNy + cuNx]); + tmp3 = ADD_O(tmp3, tmp4); + tmp2 = ADD_O(u1[ii + cuNx - 1], u1[ii - cuNx + 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp4 = ADD_O(u1[ii + cuNxNy - 1], u1[ii - cuNxNy + 1]); + tmp3 = ADD_O(tmp3, tmp4); + tmp1 = ADD_O(tmp1, tmp3); + tmp1 = FMA_D(c1, u1[ii], FMA_D(c2, tmp1, -u0[ii])); + // write final value back to global memory + if ((GET_BIT(bn_mask[ii >> 3], ii % 8)) == 0) { + u0[ii] = tmp1; + } + } +} + +// this folds in half of FCC subgrid so everything is nicely homogenous (no +// braching for stencil) +template +__global__ void KernelFoldFCC(Float* __restrict__ u1) { + int64_t const cx = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cz = blockIdx.y * cuBy2 + threadIdx.y; + // fold is along middle dimension + if ((cx < cuNx) && (cz < cuNz)) { + u1[cz * cuNxNy + (cuNy - 1) * cuNx + cx] = u1[cz * cuNxNy + (cuNy - 2) * cuNx + cx]; + } +} + +// rigid boundaries, cartesian, using adj info +template +__global__ void KernelBoundaryRigidCart( + Float* __restrict__ u0, + Float const* __restrict__ u1, + uint16_t const* __restrict__ adj_bn, + int64_t const* __restrict__ bn_ixyz, + int8_t const* __restrict__ K_bn +) { + int64_t const nb = blockIdx.x * cuBb + threadIdx.x; + if (nb < cuNb) { + int64_t const ii = bn_ixyz[nb]; + uint16_t const adj = adj_bn[nb]; + Float const K = K_bn[nb]; + + Float const _2 = 2.0; + Float const b1 = (_2 - csl2 * K); + Float const b2 = c2; + + Float tmp1 = NAN; + Float tmp2 = NAN; + tmp1 = ADD_O((Float)GET_BIT(adj, 0) * u1[ii + cuNxNy], (Float)GET_BIT(adj, 1) * u1[ii - cuNxNy]); + tmp2 = ADD_O((Float)GET_BIT(adj, 2) * u1[ii + cuNx], (Float)GET_BIT(adj, 3) * u1[ii - cuNx]); + tmp1 = ADD_O(tmp1, tmp2); + tmp2 = ADD_O((Float)GET_BIT(adj, 4) * u1[ii + 1], (Float)GET_BIT(adj, 5) * u1[ii - 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp1 = FMA_D(b1, u1[ii], FMA_D(b2, tmp1, -u0[ii])); + + // u0[ii] = partial; //write back to global memory + u0[ii] = tmp1; // write back to global memory + } +} + +// rigid boundaries, FCC, using adj info +template +__global__ void KernelBoundaryRigidFCC( + Float* __restrict__ u0, + Float const* __restrict__ u1, + uint16_t const* __restrict__ adj_bn, + int64_t const* __restrict__ bn_ixyz, + int8_t const* __restrict__ K_bn +) { + int64_t const nb = blockIdx.x * cuBb + threadIdx.x; + if (nb < cuNb) { + int64_t const ii = bn_ixyz[nb]; + uint16_t const adj = adj_bn[nb]; + Float const K = K_bn[nb]; + + Float const _2 = 2.0; + Float const b1 = (_2 - csl2 * K); + Float const b2 = c2; + + Float tmp1 = NAN; + Float tmp2 = NAN; + Float tmp3 = NAN; + Float tmp4 = NAN; + tmp1 = ADD_O((Float)GET_BIT(adj, 0) * u1[ii + cuNxNy + cuNx], (Float)GET_BIT(adj, 1) * u1[ii - cuNxNy - cuNx]); + tmp2 = ADD_O((Float)GET_BIT(adj, 2) * u1[ii + cuNx + 1], (Float)GET_BIT(adj, 3) * u1[ii - cuNx - 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp3 = ADD_O((Float)GET_BIT(adj, 4) * u1[ii + cuNxNy + 1], (Float)GET_BIT(adj, 5) * u1[ii - cuNxNy - 1]); + tmp4 = ADD_O((Float)GET_BIT(adj, 6) * u1[ii + cuNxNy - cuNx], (Float)GET_BIT(adj, 7) * u1[ii - cuNxNy + cuNx]); + tmp3 = ADD_O(tmp3, tmp4); + tmp2 = ADD_O((Float)GET_BIT(adj, 8) * u1[ii + cuNx - 1], (Float)GET_BIT(adj, 9) * u1[ii - cuNx + 1]); + tmp1 = ADD_O(tmp1, tmp2); + tmp4 = ADD_O((Float)GET_BIT(adj, 10) * u1[ii + cuNxNy - 1], (Float)GET_BIT(adj, 11) * u1[ii - cuNxNy + 1]); + tmp3 = ADD_O(tmp3, tmp4); + tmp1 = ADD_O(tmp1, tmp3); + tmp1 = FMA_D(b1, u1[ii], FMA_D(b2, tmp1, -u0[ii])); + + u0[ii] = tmp1; // write back to global memory + } +} + +// ABC loss at boundaries of simulation grid +template +__global__ void KernelBoundaryABC( + Float* __restrict__ u0, + Float const* __restrict__ u2ba, + int8_t const* __restrict__ Q_bna, + int64_t const* __restrict__ bna_ixyz +) { + int64_t const nb = blockIdx.x * cuBb + threadIdx.x; + if (nb < cuNba) { + Float const _1 = 1.0; + Float const lQ = cl * Q_bna[nb]; + int64_t const ib = bna_ixyz[nb]; + Float partial = u0[ib]; + partial = (partial + lQ * u2ba[nb]) / (_1 + lQ); + u0[ib] = partial; + } +} + +// Part of freq-dep boundary update +template +__global__ void KernelBoundaryFD( + Float* __restrict__ u0b, + Float const* u2b, + Float* __restrict__ vh1, + Float* __restrict__ gh1, + Float const* ssaf_bnl, + int8_t const* mat_bnl, + Float const* __restrict__ mat_beta, + MatQuad const* __restrict__ mat_quads +) { + int64_t const nb = blockIdx.x * cuBb + threadIdx.x; + if (nb < cuNbl) { + Float const _1 = 1.0; + Float const _2 = 2.0; + int32_t const k = mat_bnl[nb]; + Float const ssaf = ssaf_bnl[nb]; + Float const lo2Kbg = clo2 * ssaf * mat_beta[k]; + Float const fac = _2 * clo2 * ssaf / (_1 + lo2Kbg); + + Float u0bint = u0b[nb]; + Float const u2bint = u2b[nb]; + + u0bint = (u0bint + lo2Kbg * u2bint) / (_1 + lo2Kbg); + + Float vh1int[MMb]; // size has to be constant at compile time + Float gh1int[MMb]; + for (int8_t m = 0; m < cuMb[k]; m++) { // faster on average than MMb + int64_t const nbm = m * cuNbl + nb; + int32_t const mbk = k * MMb + m; + MatQuad const* tm = nullptr; + tm = &(mat_quads[mbk]); + vh1int[m] = vh1[nbm]; + gh1int[m] = gh1[nbm]; + u0bint -= fac * (_2 * (tm->bDh) * vh1int[m] - (tm->bFh) * gh1int[m]); + } + + Float const du = u0bint - u2bint; + + // NOLINTBEGIN(clang-analyzer-core.UndefinedBinaryOperatorResult) + for (int8_t m = 0; m < cuMb[k]; m++) { // faster on average than MMb + int64_t const nbm = m * cuNbl + nb; + int32_t const mbk = k * MMb + m; + MatQuad const* tm = nullptr; + tm = &(mat_quads[mbk]); + Float const vh0m = (tm->b) * du + (tm->bd) * vh1int[m] - _2 * (tm->bFh) * gh1int[m]; + gh1[nbm] = gh1int[m] + (vh0m + vh1int[m]) / _2; + vh1[nbm] = vh0m; + } + // NOLINTEND(clang-analyzer-core.UndefinedBinaryOperatorResult) + u0b[nb] = u0bint; + } +} + +// add source input (one at a time for simplicity) +template +__global__ void AddIn(Float* u0, Float sample) { + u0[0] += sample; +} + +// dst-src copy from buffer to grid +template +__global__ void CopyToGridKernel(Float* u, Float const* buffer, int64_t const* locs, int64_t N) { + int64_t const i = blockIdx.x * cuBrw + threadIdx.x; + if (i < N) { + u[locs[i]] = buffer[i]; + } +} + +// dst-src copy to buffer from grid (not needed, but to make more explicit) +template +__global__ void CopyFromGridKernel(Float* buffer, Float const* u, int64_t const* locs, int64_t N) { + int64_t const i = blockIdx.x * cuBrw + threadIdx.x; + if (i < N) { + buffer[i] = u[locs[i]]; + } +} + +// flip halos for ABCs +template +__global__ void FlipHaloXY_Zbeg(Float* __restrict__ u1) { + int64_t const cx = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cy = blockIdx.y * cuBy2 + threadIdx.y; + if ((cx < cuNx) && (cy < cuNy)) { + int64_t ii = 0; + ii = 0 * cuNxNy + cy * cuNx + cx; + u1[ii] = u1[ii + 2 * cuNxNy]; + } +} + +template +__global__ void FlipHaloXY_Zend(Float* __restrict__ u1) { + int64_t const cx = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cy = blockIdx.y * cuBy2 + threadIdx.y; + if ((cx < cuNx) && (cy < cuNy)) { + int64_t ii = 0; + ii = (cuNz - 1) * cuNxNy + cy * cuNx + cx; + u1[ii] = u1[ii - 2 * cuNxNy]; + } +} + +template +__global__ void FlipHaloXZ_Ybeg(Float* __restrict__ u1) { + int64_t const cx = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cz = blockIdx.y * cuBy2 + threadIdx.y; + if ((cx < cuNx) && (cz < cuNz)) { + int64_t ii = 0; + ii = cz * cuNxNy + 0 * cuNx + cx; + u1[ii] = u1[ii + 2 * cuNx]; + } +} + +template +__global__ void FlipHaloXZ_Yend(Float* __restrict__ u1) { + int64_t const cx = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cz = blockIdx.y * cuBy2 + threadIdx.y; + if ((cx < cuNx) && (cz < cuNz)) { + int64_t ii = 0; + ii = cz * cuNxNy + (cuNy - 1) * cuNx + cx; + u1[ii] = u1[ii - 2 * cuNx]; + } +} + +template +__global__ void FlipHaloYZ_Xbeg(Float* __restrict__ u1) { + int64_t const cy = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cz = blockIdx.y * cuBy2 + threadIdx.y; + if ((cy < cuNy) && (cz < cuNz)) { + int64_t ii = 0; + ii = cz * cuNxNy + cy * cuNx + 0; + u1[ii] = u1[ii + 2]; + } +} + +template +__global__ void FlipHaloYZ_Xend(Float* __restrict__ u1) { + int64_t const cy = blockIdx.x * cuBx2 + threadIdx.x; + int64_t const cz = blockIdx.y * cuBy2 + threadIdx.y; + if ((cy < cuNy) && (cz < cuNz)) { + int64_t ii = 0; + ii = cz * cuNxNy + cy * cuNx + (cuNx - 1); + u1[ii] = u1[ii - 2]; + } +} + +// standard error checking +// NOLINTNEXTLINE +#define gpuErrchk(ans) \ + { gpuAssert((ans), __FILE__, __LINE__); } + +inline void gpuAssert(cudaError_t code, char const* file, int line, bool abort = true) { + if (code != cudaSuccess) { + fprintf(stderr, "GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); + if (abort) { + exit(code); + } + } +} + +// print some device details +auto print_gpu_details(int i) -> uint64_t { + cudaDeviceProp prop{}; + cudaGetDeviceProperties(&prop, i); + printf("\nDevice Number: %d [%s]\n", i, prop.name); + printf(" Compute: %d.%d\n", prop.major, prop.minor); + printf(" Peak Memory Bandwidth: %.3f GB/s\n", 2.0 * prop.memoryClockRate * (prop.memoryBusWidth / 8.0) / 1.0e6); + printf( + " Total global memory: [ %.3f GB | %.3f GiB | %lu MiB ]\n", + (double)prop.totalGlobalMem / (1e9), + (double)prop.totalGlobalMem / 1073741824ULL, + prop.totalGlobalMem >> 20 + ); + printf(" Registers per block: %d\n", prop.regsPerBlock); + printf(" Concurrent Kernels: %d\n", prop.concurrentKernels); + printf(" Async Engine: %d\n", prop.asyncEngineCount); + printf("\n"); + return prop.totalGlobalMem; +} + +// input indices need to be sorted for multi-device allocation +void check_sorted(Simulation3D const* sim) { + int64_t* bn_ixyz = sim->bn_ixyz; + int64_t* bnl_ixyz = sim->bnl_ixyz; + int64_t* bna_ixyz = sim->bna_ixyz; + int64_t* in_ixyz = sim->in_ixyz; + int64_t* out_ixyz = sim->out_ixyz; + int64_t const Nb = sim->Nb; + int64_t const Nbl = sim->Nbl; + int64_t const Nba = sim->Nba; + int64_t const Ns = sim->Ns; + int64_t const Nr = sim->Nr; + for (int64_t i = 1; i < Nb; i++) { + PFFDTD_ASSERT(bn_ixyz[i] > bn_ixyz[i - 1]); // check save_gpu_folder + } + for (int64_t i = 1; i < Nbl; i++) { + PFFDTD_ASSERT(bnl_ixyz[i] > bnl_ixyz[i - 1]); + } + for (int64_t i = 1; i < Nba; i++) { + PFFDTD_ASSERT(bna_ixyz[i] > bna_ixyz[i - 1]); + } + for (int64_t i = 1; i < Ns; i++) { + PFFDTD_ASSERT(in_ixyz[i] > in_ixyz[i - 1]); + } + for (int64_t i = 1; i < Nr; i++) { + PFFDTD_ASSERT(out_ixyz[i] >= out_ixyz[i - 1]); // possible to have duplicates + } +} + +// counts for splitting data across GPUs +void split_data(Simulation3D const* sim, HostData* ghds, int ngpus) { + int64_t const Nx = sim->Nx; + int64_t const Ny = sim->Ny; + int64_t const Nz = sim->Nz; + HostData* ghd = nullptr; + // initialise + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + ghd->Nx = 0; + ghd->Nb = 0; + ghd->Nbl = 0; + ghd->Nba = 0; + ghd->Ns = 0; + ghd->Nr = 0; + } + + // split Nx layers (Nz contiguous) + int64_t const Nxm = Nx / ngpus; + int64_t const Nxl = Nx % ngpus; + + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + ghd->Nx = Nxm; + } + for (int gid = 0; gid < Nxl; gid++) { + ghd = &ghds[gid]; + ghd->Nx += 1; + } + int64_t Nx_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Nx[%d]=%ld, Nx=%ld\n", gid, gid, ghd->Nx, Nx); + Nx_check += ghd->Nx; + } + PFFDTD_ASSERT(Nx_check == Nx); + + // now count Nr,Ns,Nb for each device + auto Nxcc = std::vector(static_cast(ngpus)); + Nxcc[0] = ghds[0].Nx; + printf("Nxcc[%d]=%ld\n", 0, Nxcc[0]); + for (int gid = 1; gid < ngpus; gid++) { + ghd = &ghds[gid]; + Nxcc[gid] = ghd->Nx + Nxcc[gid - 1]; + printf("Nxcc[%d]=%ld\n", gid, Nxcc[gid]); + } + + // bn_ixyz - Nb + int64_t* bn_ixyz = sim->bn_ixyz; + int64_t const Nb = sim->Nb; + { + int gid = 0; + for (int64_t i = 0; i < Nb; i++) { + while (bn_ixyz[i] >= Nxcc[gid] * Ny * Nz) { + gid++; + } + (ghds[gid].Nb)++; + } + } + int64_t Nb_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Nb[%d]=%ld, Nb=%ld\n", gid, gid, ghd->Nb, Nb); + Nb_check += ghd->Nb; + } + PFFDTD_ASSERT(Nb_check == Nb); + + // bnl_ixyz - Nbl + int64_t* bnl_ixyz = sim->bnl_ixyz; + int64_t const Nbl = sim->Nbl; + { + int gid = 0; + for (int64_t i = 0; i < Nbl; i++) { + while (bnl_ixyz[i] >= Nxcc[gid] * Ny * Nz) { + gid++; + } + (ghds[gid].Nbl)++; + } + } + int64_t Nbl_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Nbl[%d]=%ld, Nbl=%ld\n", gid, gid, ghd->Nbl, Nbl); + Nbl_check += ghd->Nbl; + } + PFFDTD_ASSERT(Nbl_check == Nbl); + + // bna_ixyz - Nba + int64_t* bna_ixyz = sim->bna_ixyz; + int64_t const Nba = sim->Nba; + { + int gid = 0; + for (int64_t i = 0; i < Nba; i++) { + while (bna_ixyz[i] >= Nxcc[gid] * Ny * Nz) { + gid++; + } + (ghds[gid].Nba)++; + } + } + int64_t Nba_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Nba[%d]=%ld, Nbl=%ld\n", gid, gid, ghd->Nba, Nba); + Nba_check += ghd->Nba; + } + PFFDTD_ASSERT(Nba_check == Nba); + + // in_ixyz - Ns + int64_t* in_ixyz = sim->in_ixyz; + int64_t const Ns = sim->Ns; + { + int gid = 0; + for (int64_t i = 0; i < Ns; i++) { + while (in_ixyz[i] >= Nxcc[gid] * Ny * Nz) { + gid++; + } + (ghds[gid].Ns)++; + } + } + int64_t Ns_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Ns[%d]=%ld, Ns=%ld\n", gid, gid, ghd->Ns, Ns); + Ns_check += ghd->Ns; + } + PFFDTD_ASSERT(Ns_check == Ns); + + // out_ixyz - Nr + int64_t* out_ixyz = sim->out_ixyz; + int64_t const Nr = sim->Nr; + { + int gid = 0; + for (int64_t i = 0; i < Nr; i++) { + while (out_ixyz[i] >= Nxcc[gid] * Ny * Nz) { + gid++; + } + (ghds[gid].Nr)++; + } + } + int64_t Nr_check = 0; + for (int gid = 0; gid < ngpus; gid++) { + ghd = &ghds[gid]; + printf("gid=%d, Nr[%d]=%ld, Nr=%ld\n", gid, gid, ghd->Nr, Nr); + Nr_check += ghd->Nr; + } + PFFDTD_ASSERT(Nr_check == Nr); +} + +// run the sim! +auto run(Simulation3D const& sim) -> double { + // if you want to test synchronous, env variable for that + char const* s = getenv("CUDA_LAUNCH_BLOCKING"); + if (s != nullptr) { + if (s[0] == '1') { + printf("******************SYNCHRONOUS (DEBUG " + "ONLY!!!)*********************\n"); + printf("...continue?\n"); + getchar(); + } + } + + PFFDTD_ASSERT((sim.fcc_flag != 1)); // uses either cartesian or FCC folded grid + + int ngpus = 0; + int max_ngpus = 0; + cudaGetDeviceCount(&max_ngpus); // control outside with CUDA_VISIBLE_DEVICES + ngpus = max_ngpus; + PFFDTD_ASSERT(ngpus < (sim.Nx)); + DeviceData* gds = nullptr; + allocate_zeros((void**)&gds, ngpus * sizeof(DeviceData)); + HostData* ghds = nullptr; + allocate_zeros((void**)&ghds, ngpus * sizeof(HostData)); // one bit per + + if (ngpus > 1) { + check_sorted(&sim); // needs to be sorted for multi-GPU + } + + // get local counts for Nx,Nb,Nr,Ns + split_data(&sim, ghds, ngpus); + + for (int gid = 0; gid < ngpus; gid++) { + gds[gid].totalmembytes = print_gpu_details(gid); + } + + Real lo2 = sim.lo2; + Real a1 = sim.a1; + Real a2 = sim.a2; + Real l = sim.l; + Real sl2 = sim.sl2; + + // timing stuff + double time_elapsed = 0.0; + double time_elapsed_bn = 0.0; + double time_elapsed_sample = NAN; + double time_elapsed_sample_bn = NAN; + double time_elapsed_air = 0.0; // feed into print/process + double time_elapsed_sample_air = NAN; // feed into print/process + float millis_since_start = NAN; + float millis_since_sample_start = NAN; + + printf("a1 = %.16g\n", a1); + printf("a2 = %.16g\n", a2); + + // start moving data to GPUs + for (int gid = 0; gid < ngpus; gid++) { + HostData* ghd = &(ghds[gid]); + printf("GPU %d -- ", gid); + printf("Nx=%ld Ns=%ld Nr=%ld Nb=%ld Nbl=%ld Nba=%ld\n", ghd->Nx, ghd->Ns, ghd->Nr, ghd->Nb, ghd->Nbl, ghd->Nba); + } + + int64_t Ns_read = 0; + int64_t Nr_read = 0; + int64_t Nb_read = 0; + int64_t Nbl_read = 0; + int64_t Nba_read = 0; + int64_t Nx_read = 0; + int64_t Nx_pos = 0; + // uint64_t Nx_pos2=0; + + Real* u_out_buf = nullptr; + gpuErrchk(cudaMallocHost(&u_out_buf, (size_t)(sim.Nr * sizeof(Real)))); + memset(u_out_buf, 0, (size_t)(sim.Nr * sizeof(Real))); // set floats to zero + + int64_t Nzy = (sim.Nz) * (sim.Ny); // area-slice + + // here we recalculate indices to move to devices + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + + DeviceData* gd = &(gds[gid]); + HostData* ghd = &(ghds[gid]); + printf("---------\n"); + printf("GPU %d\n", gid); + printf("---------\n"); + + printf("Nx to read = %ld\n", ghd->Nx); + printf("Nb to read = %ld\n", ghd->Nb); + printf("Nbl to read = %ld\n", ghd->Nbl); + printf("Nba to read = %ld\n", ghd->Nba); + printf("Ns to read = %ld\n", ghd->Ns); + printf("Nr to read = %ld\n", ghd->Nr); + + // Nxh (effective Nx with extra halos) + ghd->Nxh = ghd->Nx; + if (gid > 0) { + (ghd->Nxh)++; // add bottom halo + } + if (gid < ngpus - 1) { + (ghd->Nxh)++; // add top halo + } + // calculate Npts for this device + ghd->Npts = Nzy * (ghd->Nxh); + // boundary mask + ghd->Nbm = CU_DIV_CEIL(ghd->Npts, 8); + + printf("Nx=%ld Ns=%ld Nr=%ld Nb=%ld, Npts=%ld\n", ghd->Nx, ghd->Ns, ghd->Nr, ghd->Nb, ghd->Npts); + + // aliased pointers (to memory already allocated) + ghd->in_sigs = sim.in_sigs + Ns_read * sim.Nt; + ghd->ssaf_bnl = sim.ssaf_bnl + Nbl_read; + ghd->adj_bn = sim.adj_bn + Nb_read; + ghd->mat_bnl = sim.mat_bnl + Nbl_read; + ghd->K_bn = sim.K_bn + Nb_read; + ghd->Q_bna = sim.Q_bna + Nba_read; + ghd->u_out = sim.u_out + Nr_read * sim.Nt; + ghd->u_out_buf = u_out_buf + Nr_read; + + // recalculate indices, these are associated host versions to copy over to + // devices + allocate_zeros((void**)&(ghd->bn_ixyz), ghd->Nb * sizeof(int64_t)); + allocate_zeros((void**)&(ghd->bnl_ixyz), ghd->Nbl * sizeof(int64_t)); + allocate_zeros((void**)&(ghd->bna_ixyz), ghd->Nba * sizeof(int64_t)); + allocate_zeros((void**)&(ghd->bn_mask), ghd->Nbm * sizeof(uint8_t)); + allocate_zeros((void**)&(ghd->in_ixyz), ghd->Ns * sizeof(int64_t)); + allocate_zeros((void**)&(ghd->out_ixyz), ghd->Nr * sizeof(int64_t)); + + int64_t const offset = Nzy * Nx_pos; + for (int64_t nb = 0; nb < (ghd->Nb); nb++) { + int64_t const ii = sim.bn_ixyz[nb + Nb_read]; // global index + int64_t const jj = ii - offset; // local index + PFFDTD_ASSERT(jj >= 0); + PFFDTD_ASSERT(jj < ghd->Npts); + ghd->bn_ixyz[nb] = jj; + SET_BIT_VAL(ghd->bn_mask[jj >> 3], jj % 8, GET_BIT(sim.bn_mask[ii >> 3], ii % 8)); // set bit + } + for (int64_t nb = 0; nb < (ghd->Nbl); nb++) { + int64_t const ii = sim.bnl_ixyz[nb + Nbl_read]; // global index + int64_t const jj = ii - offset; // local index + PFFDTD_ASSERT(jj >= 0); + PFFDTD_ASSERT(jj < ghd->Npts); + ghd->bnl_ixyz[nb] = jj; + } + + for (int64_t nb = 0; nb < (ghd->Nba); nb++) { + int64_t const ii = sim.bna_ixyz[nb + Nba_read]; // global index + int64_t const jj = ii - offset; // local index + PFFDTD_ASSERT(jj >= 0); + PFFDTD_ASSERT(jj < ghd->Npts); + ghd->bna_ixyz[nb] = jj; + } + + for (int64_t ns = 0; ns < (ghd->Ns); ns++) { + int64_t const ii = sim.in_ixyz[ns + Ns_read]; + int64_t const jj = ii - offset; + PFFDTD_ASSERT(jj >= 0); + PFFDTD_ASSERT(jj < ghd->Npts); + ghd->in_ixyz[ns] = jj; + } + for (int64_t nr = 0; nr < (ghd->Nr); nr++) { + int64_t const ii = sim.out_ixyz[nr + Nr_read]; + int64_t const jj = ii - offset; + PFFDTD_ASSERT(jj >= 0); + PFFDTD_ASSERT(jj < ghd->Npts); + ghd->out_ixyz[nr] = jj; + } + + gpuErrchk(cudaMalloc(&(gd->u0), (size_t)((ghd->Npts) * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u0, 0, (size_t)((ghd->Npts) * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->u1), (size_t)((ghd->Npts) * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u1, 0, (size_t)((ghd->Npts) * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->K_bn), (size_t)(ghd->Nb * sizeof(int8_t)))); + gpuErrchk(cudaMemcpy(gd->K_bn, ghd->K_bn, ghd->Nb * sizeof(int8_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->ssaf_bnl), (size_t)(ghd->Nbl * sizeof(Real)))); + gpuErrchk(cudaMemcpy(gd->ssaf_bnl, ghd->ssaf_bnl, ghd->Nbl * sizeof(Real), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->u0b), (size_t)(ghd->Nbl * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u0b, 0, (size_t)(ghd->Nbl * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->u1b), (size_t)(ghd->Nbl * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u1b, 0, (size_t)(ghd->Nbl * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->u2b), (size_t)(ghd->Nbl * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u2b, 0, (size_t)(ghd->Nbl * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->u2ba), (size_t)(ghd->Nba * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u2ba, 0, (size_t)(ghd->Nba * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->vh1), (size_t)(ghd->Nbl * MMb * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->vh1, 0, (size_t)(ghd->Nbl * MMb * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->gh1), (size_t)(ghd->Nbl * MMb * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->gh1, 0, (size_t)(ghd->Nbl * MMb * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->u_out_buf), (size_t)(ghd->Nr * sizeof(Real)))); + gpuErrchk(cudaMemset(gd->u_out_buf, 0, (size_t)(ghd->Nr * sizeof(Real)))); + + gpuErrchk(cudaMalloc(&(gd->bn_ixyz), (size_t)(ghd->Nb * sizeof(int64_t)))); + gpuErrchk(cudaMemcpy(gd->bn_ixyz, ghd->bn_ixyz, (size_t)ghd->Nb * sizeof(int64_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->bnl_ixyz), (size_t)(ghd->Nbl * sizeof(int64_t)))); + gpuErrchk(cudaMemcpy(gd->bnl_ixyz, ghd->bnl_ixyz, (size_t)ghd->Nbl * sizeof(int64_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->bna_ixyz), (size_t)(ghd->Nba * sizeof(int64_t)))); + gpuErrchk(cudaMemcpy(gd->bna_ixyz, ghd->bna_ixyz, (size_t)ghd->Nba * sizeof(int64_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->Q_bna), (size_t)(ghd->Nba * sizeof(int8_t)))); + gpuErrchk(cudaMemcpy(gd->Q_bna, ghd->Q_bna, ghd->Nba * sizeof(int8_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->out_ixyz), (size_t)(ghd->Nr * sizeof(int64_t)))); + gpuErrchk(cudaMemcpy(gd->out_ixyz, ghd->out_ixyz, (size_t)ghd->Nr * sizeof(int64_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->adj_bn), (size_t)(ghd->Nb * sizeof(uint16_t)))); + gpuErrchk(cudaMemcpy(gd->adj_bn, ghd->adj_bn, (size_t)ghd->Nb * sizeof(uint16_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->mat_bnl), (size_t)(ghd->Nbl * sizeof(int8_t)))); + gpuErrchk(cudaMemcpy(gd->mat_bnl, ghd->mat_bnl, (size_t)ghd->Nbl * sizeof(int8_t), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->mat_beta), (size_t)sim.Nm * sizeof(Real))); + gpuErrchk(cudaMemcpy(gd->mat_beta, sim.mat_beta, (size_t)sim.Nm * sizeof(Real), cudaMemcpyHostToDevice)); + + gpuErrchk(cudaMalloc(&(gd->mat_quads), (size_t)sim.Nm * MMb * sizeof(MatQuad))); + gpuErrchk( + cudaMemcpy(gd->mat_quads, sim.mat_quads, (size_t)sim.Nm * MMb * sizeof(MatQuad), cudaMemcpyHostToDevice) + ); + + gpuErrchk(cudaMalloc(&(gd->bn_mask), (size_t)(ghd->Nbm * sizeof(uint8_t)))); + gpuErrchk(cudaMemcpy(gd->bn_mask, ghd->bn_mask, (size_t)ghd->Nbm * sizeof(uint8_t), cudaMemcpyHostToDevice)); + + Ns_read += ghd->Ns; + Nr_read += ghd->Nr; + Nb_read += ghd->Nb; + Nbl_read += ghd->Nbl; + Nba_read += ghd->Nba; + Nx_read += ghd->Nx; + Nx_pos = Nx_read - 1; // back up one at subsequent stage + + printf("Nx_read = %ld\n", Nx_read); + printf("Nb_read = %ld\n", Nb_read); + printf("Nbl_read = %ld\n", Nbl_read); + printf("Ns_read = %ld\n", Ns_read); + printf("Nr_read = %ld\n", Nr_read); + + printf("Global memory allocation done\n"); + printf("\n"); + + // swapping x and z here (CUDA has first dim contiguous) + gpuErrchk(cudaMemcpyToSymbol(cuNx, &(sim.Nz), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNy, &(sim.Ny), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNz, &(ghd->Nxh), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNb, &(ghd->Nb), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNbl, &(ghd->Nbl), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNba, &(ghd->Nba), sizeof(int64_t))); + gpuErrchk(cudaMemcpyToSymbol(cuMb, sim.Mb, sim.Nm * sizeof(int8_t))); + gpuErrchk(cudaMemcpyToSymbol(cuNxNy, &Nzy, + sizeof(int64_t))); // same for all devices + + gpuErrchk(cudaMemcpyToSymbol(c1, &a1, sizeof(Real))); + gpuErrchk(cudaMemcpyToSymbol(c2, &a2, sizeof(Real))); + gpuErrchk(cudaMemcpyToSymbol(cl, &l, sizeof(Real))); + gpuErrchk(cudaMemcpyToSymbol(csl2, &sl2, sizeof(Real))); + gpuErrchk(cudaMemcpyToSymbol(clo2, &lo2, sizeof(Real))); + + printf("Constant memory loaded\n"); + printf("\n"); + + // threads grids and blocks (swap x and z) + int64_t const cuGx = CU_DIV_CEIL(sim.Nz - 2, cuBx); + int64_t const cuGy = CU_DIV_CEIL(sim.Ny - 2, cuBy); + int64_t const cuGz = CU_DIV_CEIL(ghd->Nxh - 2, cuBz); + int64_t const cuGr = CU_DIV_CEIL(ghd->Nr, cuBrw); + int64_t const cuGb = CU_DIV_CEIL(ghd->Nb, cuBb); + int64_t const cuGbl = CU_DIV_CEIL(ghd->Nbl, cuBb); + int64_t const cuGba = CU_DIV_CEIL(ghd->Nba, cuBb); + + int64_t const cuGx2 = CU_DIV_CEIL(sim.Nz, cuBx2); // full face + int64_t const cuGz2 = CU_DIV_CEIL(ghd->Nxh, cuBy2); // full face + + PFFDTD_ASSERT(cuGx >= 1); + PFFDTD_ASSERT(cuGy >= 1); + PFFDTD_ASSERT(cuGz >= 1); + PFFDTD_ASSERT(cuGr >= 1); + PFFDTD_ASSERT(cuGb >= 1); + PFFDTD_ASSERT(cuGbl >= 1); + PFFDTD_ASSERT(cuGba >= 1); + + gd->block_dim_air = dim3(cuBx, cuBy, cuBz); + gd->block_dim_readout = dim3(cuBrw, 1, 1); + gd->block_dim_bn = dim3(cuBb, 1, 1); + + gd->grid_dim_air = dim3(cuGx, cuGy, cuGz); + gd->grid_dim_readout = dim3(cuGr, 1, 1); + gd->grid_dim_bn = dim3(cuGb, 1, 1); + gd->grid_dim_bnl = dim3(cuGbl, 1, 1); + gd->grid_dim_bna = dim3(cuGba, 1, 1); + + gd->block_dim_halo_xy = dim3(cuBx2, cuBy2, 1); + gd->block_dim_halo_yz = dim3(cuBx2, cuBy2, 1); + gd->block_dim_halo_xz = dim3(cuBx2, cuBy2, 1); + gd->grid_dim_halo_xy = dim3(CU_DIV_CEIL(sim.Nz, cuBx2), CU_DIV_CEIL(sim.Ny, cuBy2), 1); + gd->grid_dim_halo_yz = dim3(CU_DIV_CEIL(sim.Ny, cuBx2), CU_DIV_CEIL(ghd->Nxh, cuBy2), 1); + gd->grid_dim_halo_xz = dim3(CU_DIV_CEIL(sim.Nz, cuBx2), CU_DIV_CEIL(ghd->Nxh, cuBy2), 1); + + gd->block_dim_fold = dim3(cuBx2, cuBy2, 1); + gd->grid_dim_fold = dim3(cuGx2, cuGz2, 1); + + // create streams + gpuErrchk(cudaStreamCreate(&(gd->cuStream_air))); + gpuErrchk(cudaStreamCreate(&(gd->cuStream_bn))); // no priority + + // cuda events + gpuErrchk(cudaEventCreate(&(gd->cuEv_air_start))); + gpuErrchk(cudaEventCreate(&(gd->cuEv_air_end))); + gpuErrchk(cudaEventCreate(&(gd->cuEv_bn_roundtrip_start))); + gpuErrchk(cudaEventCreate(&(gd->cuEv_bn_roundtrip_end))); + gpuErrchk(cudaEventCreate(&(gd->cuEv_readout_end))); + } + PFFDTD_ASSERT(Nb_read == sim.Nb); + PFFDTD_ASSERT(Nbl_read == sim.Nbl); + PFFDTD_ASSERT(Nba_read == sim.Nba); + PFFDTD_ASSERT(Nr_read == sim.Nr); + PFFDTD_ASSERT(Ns_read == sim.Ns); + PFFDTD_ASSERT(Nx_read == sim.Nx); + + // these will be on first GPU only + cudaEvent_t cuEv_main_start = nullptr; + cudaEvent_t cuEv_main_end = nullptr; + cudaEvent_t cuEv_main_sample_start = nullptr; + cudaEvent_t cuEv_main_sample_end = nullptr; + gpuErrchk(cudaSetDevice(0)); + gpuErrchk(cudaEventCreate(&cuEv_main_start)); + gpuErrchk(cudaEventCreate(&cuEv_main_end)); + gpuErrchk(cudaEventCreate(&cuEv_main_sample_start)); + gpuErrchk(cudaEventCreate(&cuEv_main_sample_end)); + + for (int64_t n = 0; n < sim.Nt; n++) { // loop over time-steps + for (int gid = 0; gid < ngpus; gid++) { // loop over GPUs (one thread launches all kernels) + gpuErrchk(cudaSetDevice(gid)); + DeviceData* gd = &(gds[gid]); // get struct of device pointers + HostData* ghd = &(ghds[gid]); // get struct of host points (corresponding to device) + + // start first timer + if (gid == 0) { + if (n == 0) { + // not sure if to put on stream, check slides again + gpuErrchk(cudaEventRecord(cuEv_main_start, nullptr)); + } + gpuErrchk(cudaEventRecord(cuEv_main_sample_start, nullptr)); + } + // boundary updates (using intermediate buffer) + gpuErrchk(cudaEventRecord(gd->cuEv_bn_roundtrip_start, gd->cuStream_bn)); + + // boundary updates + if (sim.fcc_flag == 0) { + KernelBoundaryRigidCart<<grid_dim_bn, gd->block_dim_bn, 0, gd->cuStream_bn>>>( + gd->u0, + gd->u1, + gd->adj_bn, + gd->bn_ixyz, + gd->K_bn + ); + } else { + KernelFoldFCC<<grid_dim_fold, gd->block_dim_fold, 0, gd->cuStream_bn>>>(gd->u1); + KernelBoundaryRigidFCC<<grid_dim_bn, gd->block_dim_bn, 0, gd->cuStream_bn>>>( + gd->u0, + gd->u1, + gd->adj_bn, + gd->bn_ixyz, + gd->K_bn + ); + } + // using buffer to then update FD boundaries + CopyFromGridKernel<<grid_dim_bnl, gd->block_dim_bn, 0, gd->cuStream_bn>>>( + gd->u0b, + gd->u0, + gd->bnl_ixyz, + ghd->Nbl + ); + // possible this could be moved to host + KernelBoundaryFD<<grid_dim_bnl, gd->block_dim_bn, 0, gd->cuStream_bn>>>( + gd->u0b, + gd->u2b, + gd->vh1, + gd->gh1, + gd->ssaf_bnl, + gd->mat_bnl, + gd->mat_beta, + gd->mat_quads + ); + // copy to back to grid + CopyToGridKernel<<grid_dim_bnl, gd->block_dim_bn, 0, gd->cuStream_bn>>>( + gd->u0, + gd->u0b, + gd->bnl_ixyz, + ghd->Nbl + ); + gpuErrchk(cudaEventRecord(gd->cuEv_bn_roundtrip_end, gd->cuStream_bn)); + + // air updates (including source + gpuErrchk(cudaStreamWaitEvent(gd->cuStream_air, gd->cuEv_bn_roundtrip_end, + 0)); // might as well wait + // run air kernel (with mask) + gpuErrchk(cudaEventRecord(gd->cuEv_air_start, gd->cuStream_air)); + + // for absorbing boundaries at boundaries of grid + CopyFromGridKernel<<grid_dim_bna, gd->block_dim_bn, 0, gd->cuStream_air>>>( + gd->u2ba, + gd->u0, + gd->bna_ixyz, + ghd->Nba + ); + if (gid == 0) { + FlipHaloXY_Zbeg<<grid_dim_halo_xy, gd->block_dim_halo_xy, 0, gd->cuStream_air>>>(gd->u1); + } + if (gid == ngpus - 1) { + FlipHaloXY_Zend<<grid_dim_halo_xy, gd->block_dim_halo_xy, 0, gd->cuStream_air>>>(gd->u1); + } + FlipHaloXZ_Ybeg<<grid_dim_halo_xz, gd->block_dim_halo_xz, 0, gd->cuStream_air>>>(gd->u1); + if (sim.fcc_flag == 0) { + FlipHaloXZ_Yend<<grid_dim_halo_xz, gd->block_dim_halo_xz, 0, gd->cuStream_air>>>(gd->u1); + } + FlipHaloYZ_Xbeg<<grid_dim_halo_yz, gd->block_dim_halo_yz, 0, gd->cuStream_air>>>(gd->u1); + FlipHaloYZ_Xend<<grid_dim_halo_yz, gd->block_dim_halo_yz, 0, gd->cuStream_air>>>(gd->u1); + + // injecting source first, negating sample to add it in first (NB source + // on different stream than bn) + for (int64_t ns = 0; ns < ghd->Ns; ns++) { + AddIn<<<1, 1, 0, gd->cuStream_air>>>(gd->u0 + ghd->in_ixyz[ns], (Real)(-(ghd->in_sigs[ns * sim.Nt + n]))); + } + // now air updates (not conflicting with bn updates because of bn_mask) + if (sim.fcc_flag == 0) { + KernelAirCart<<grid_dim_air, gd->block_dim_air, 0, gd->cuStream_air>>>(gd->u0, gd->u1, gd->bn_mask); + } else { + KernelAirFCC<<grid_dim_air, gd->block_dim_air, 0, gd->cuStream_air>>>(gd->u0, gd->u1, gd->bn_mask); + } + // boundary ABC loss + KernelBoundaryABC<<grid_dim_bna, gd->block_dim_bn, 0, gd->cuStream_air>>>( + gd->u0, + gd->u2ba, + gd->Q_bna, + gd->bna_ixyz + ); + gpuErrchk(cudaEventRecord(gd->cuEv_air_end, gd->cuStream_air)); // for timing + + // readouts + CopyFromGridKernel<<grid_dim_readout, gd->block_dim_readout, 0, gd->cuStream_bn>>>( + gd->u_out_buf, + gd->u1, + gd->out_ixyz, + ghd->Nr + ); + // then async memory copy of outputs (not really async because on same + // stream as CopyFromGridKernel) + gpuErrchk(cudaMemcpyAsync( + ghd->u_out_buf, + gd->u_out_buf, + ghd->Nr * sizeof(Real), + cudaMemcpyDeviceToHost, + gd->cuStream_bn + )); + gpuErrchk(cudaEventRecord(gd->cuEv_readout_end, gd->cuStream_bn)); + } + + // readouts + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + DeviceData* gd = &(gds[gid]); + HostData* ghd = &(ghds[gid]); + gpuErrchk(cudaEventSynchronize(gd->cuEv_readout_end)); + // copy grid points off output buffer + for (int64_t nr = 0; nr < ghd->Nr; nr++) { + ghd->u_out[nr * sim.Nt + n] = (double)(ghd->u_out_buf[nr]); + } + } + // synchronise streams + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + DeviceData* gd = &(gds[gid]); // don't really need to set gpu device to sync + gpuErrchk(cudaStreamSynchronize(gd->cuStream_air)); // interior complete + gpuErrchk(cudaStreamSynchronize(gd->cuStream_bn)); // transfer complete + } + // dst then src, stream with src gives best performance (CUDA thing) + + // now asynchronous halo swaps, even/odd pairs concurrent + // these are not async to rest of scheme, just async to other swaps + + // copy forward (even) + for (int gid = 0; gid < ngpus - 1; gid += 2) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaMemcpyPeerAsync( + gds[gid + 1].u0, + gid + 1, + gds[gid].u0 + Nzy * (ghds[gid].Nxh - 2), + gid, + (size_t)(Nzy * sizeof(Real)), + gds[gid].cuStream_bn + )); + } + // copy back (odd) + for (int gid = 1; gid < ngpus; gid += 2) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaMemcpyPeerAsync( + gds[gid - 1].u0 + Nzy * (ghds[gid - 1].Nxh - 1), + gid - 1, + gds[gid].u0 + Nzy, + gid, + (size_t)(Nzy * sizeof(Real)), + gds[gid].cuStream_bn + )); + } + // copy forward (odd) + for (int gid = 1; gid < ngpus - 1; gid += 2) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaMemcpyPeerAsync( + gds[gid + 1].u0, + gid + 1, + gds[gid].u0 + Nzy * (ghds[gid].Nxh - 2), + gid, + (size_t)(Nzy * sizeof(Real)), + gds[gid].cuStream_bn + )); + } + // copy back (even) -- skip zero + for (int gid = 2; gid < ngpus; gid += 2) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaMemcpyPeerAsync( + gds[gid - 1].u0 + Nzy * (ghds[gid - 1].Nxh - 1), + gid - 1, + gds[gid].u0 + Nzy, + gid, + (size_t)(Nzy * sizeof(Real)), + gds[gid].cuStream_bn + )); + } + + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + DeviceData* gd = &(gds[gid]); + gpuErrchk(cudaStreamSynchronize(gd->cuStream_bn)); // transfer complete + } + for (int gid = 0; gid < ngpus; gid++) { + DeviceData* gd = &(gds[gid]); + // update pointers + Real* tmp_ptr = nullptr; + tmp_ptr = gd->u1; + gd->u1 = gd->u0; + gd->u0 = tmp_ptr; + + // will use extra vector for this (simpler than extra copy kernel) + tmp_ptr = gd->u2b; + gd->u2b = gd->u1b; + gd->u1b = gd->u0b; + gd->u0b = tmp_ptr; + + if (gid == 0) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaEventRecord(cuEv_main_sample_end, nullptr)); + } + } + + { + // timing only on gpu0 + gpuErrchk(cudaSetDevice(0)); + DeviceData* gd = &(gds[0]); + gpuErrchk(cudaEventSynchronize(cuEv_main_sample_end)); // not sure this is correct + gpuErrchk(cudaEventElapsedTime(&millis_since_start, cuEv_main_start, cuEv_main_sample_end)); + gpuErrchk(cudaEventElapsedTime(&millis_since_sample_start, cuEv_main_sample_start, cuEv_main_sample_end)); + + time_elapsed = millis_since_start / 1000; + time_elapsed_sample = millis_since_sample_start / 1000; + + float millis_air = NAN; + float millis_bn = NAN; + gpuErrchk(cudaEventElapsedTime(&millis_air, gd->cuEv_air_start, gd->cuEv_air_end)); + time_elapsed_sample_air = 0.001 * millis_air; + time_elapsed_air += time_elapsed_sample_air; + + // not full picutre, only first gpu + gpuErrchk(cudaEventElapsedTime(&millis_bn, gd->cuEv_bn_roundtrip_start, gd->cuEv_bn_roundtrip_end)); + + time_elapsed_sample_bn = millis_bn / 1000.0; + time_elapsed_bn += time_elapsed_sample_bn; + + print_progress( + n, + sim.Nt, + sim.Npts, + sim.Nb, + time_elapsed, + time_elapsed_sample, + time_elapsed_air, + time_elapsed_sample_air, + time_elapsed_bn, + time_elapsed_sample_bn, + ngpus + ); + } + } + printf("\n"); + + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaPeekAtLastError()); + gpuErrchk(cudaDeviceSynchronize()); + } + { + // timing (on device 0) + gpuErrchk(cudaSetDevice(0)); + gpuErrchk(cudaEventRecord(cuEv_main_end)); + gpuErrchk(cudaEventSynchronize(cuEv_main_end)); + + gpuErrchk(cudaEventElapsedTime(&millis_since_start, cuEv_main_start, cuEv_main_end)); + time_elapsed = millis_since_start / 1000; + } + + /*------------------------ + * FREE WILLY + ------------------------*/ + gpuErrchk(cudaSetDevice(0)); + gpuErrchk(cudaEventDestroy(cuEv_main_start)); + gpuErrchk(cudaEventDestroy(cuEv_main_end)); + gpuErrchk(cudaEventDestroy(cuEv_main_sample_start)); + gpuErrchk(cudaEventDestroy(cuEv_main_sample_end)); + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + DeviceData* gd = &(gds[gid]); + HostData* ghd = &(ghds[gid]); + // cleanup streams + gpuErrchk(cudaStreamDestroy(gd->cuStream_air)); + gpuErrchk(cudaStreamDestroy(gd->cuStream_bn)); + + // cleanup events + gpuErrchk(cudaEventDestroy(gd->cuEv_air_start)); + gpuErrchk(cudaEventDestroy(gd->cuEv_air_end)); + gpuErrchk(cudaEventDestroy(gd->cuEv_bn_roundtrip_start)); + gpuErrchk(cudaEventDestroy(gd->cuEv_bn_roundtrip_end)); + gpuErrchk(cudaEventDestroy(gd->cuEv_readout_end)); + + // free memory + gpuErrchk(cudaFree(gd->u0)); + gpuErrchk(cudaFree(gd->u1)); + gpuErrchk(cudaFree(gd->out_ixyz)); + gpuErrchk(cudaFree(gd->bn_ixyz)); + gpuErrchk(cudaFree(gd->bnl_ixyz)); + gpuErrchk(cudaFree(gd->bna_ixyz)); + gpuErrchk(cudaFree(gd->Q_bna)); + gpuErrchk(cudaFree(gd->adj_bn)); + gpuErrchk(cudaFree(gd->mat_bnl)); + gpuErrchk(cudaFree(gd->K_bn)); + gpuErrchk(cudaFree(gd->ssaf_bnl)); + gpuErrchk(cudaFree(gd->mat_beta)); + gpuErrchk(cudaFree(gd->mat_quads)); + gpuErrchk(cudaFree(gd->bn_mask)); + gpuErrchk(cudaFree(gd->u0b)); + gpuErrchk(cudaFree(gd->u1b)); + gpuErrchk(cudaFree(gd->u2b)); + gpuErrchk(cudaFree(gd->u2ba)); + gpuErrchk(cudaFree(gd->vh1)); + gpuErrchk(cudaFree(gd->gh1)); + gpuErrchk(cudaFree(gd->u_out_buf)); + free(ghd->bn_mask); + free(ghd->bn_ixyz); + free(ghd->bnl_ixyz); + free(ghd->bna_ixyz); + free(ghd->in_ixyz); + free(ghd->out_ixyz); + } + gpuErrchk(cudaFreeHost(u_out_buf)); + free(gds); + free(ghds); + + // reset after frees (for some reason it conflicts with cudaFreeHost) + for (int gid = 0; gid < ngpus; gid++) { + gpuErrchk(cudaSetDevice(gid)); + gpuErrchk(cudaDeviceReset()); + } + + printf("Boundary loop: %.6fs, %.2f Mvox/s\n", time_elapsed_bn, sim.Nb * sim.Nt / 1e6 / time_elapsed_bn); + printf("Air update: %.6fs, %.2f Mvox/s\n", time_elapsed_air, sim.Npts * sim.Nt / 1e6 / time_elapsed_air); + printf("Combined (total): %.6fs, %.2f Mvox/s\n", time_elapsed, sim.Npts * sim.Nt / 1e6 / time_elapsed); + return time_elapsed; +} + +} // namespace pffdtd diff --git a/src/cpp/main_3d/engine_gpu.hpp b/src/cpp/main_3d/engine_gpu.hpp new file mode 100644 index 0000000..eb37c5b --- /dev/null +++ b/src/cpp/main_3d/engine_gpu.hpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2021 Brian Hamilton +// Description: GPU-based implementation of FDTD engine (using CUDA). + +#pragma once + +#include "pffdtd/simulation_3d.hpp" + +#if !PFFDTD_HAS_CUDA + #error "CUDA must be enabled in the Makefile" +#endif + +namespace pffdtd { +auto run(Simulation3D const& sim) -> double; +} diff --git a/src/cpp/main_3d/main.cpp b/src/cpp/main_3d/main.cpp index 9a6a15d..8f9d57e 100644 --- a/src/cpp/main_3d/main.cpp +++ b/src/cpp/main_3d/main.cpp @@ -5,12 +5,12 @@ #include "pffdtd/simulation_3d.hpp" #include "pffdtd/utility.hpp" -#ifndef USING_CUDA - #error "forgot to define USING_CUDA" +#ifndef PFFDTD_HAS_CUDA + #error "forgot to define PFFDTD_HAS_CUDA" #endif -#if USING_CUDA - #include "engine_cuda.hpp" +#if PFFDTD_HAS_CUDA + #include "engine_gpu.hpp" #else #include "engine_cpu.hpp" #endif diff --git a/src/cpp/pffdtd/assert.hpp b/src/cpp/pffdtd/assert.hpp new file mode 100644 index 0000000..628133d --- /dev/null +++ b/src/cpp/pffdtd/assert.hpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024 Tobias Hienzsch + +#pragma once + +#undef NDEBUG +#include + +#define PFFDTD_ASSERT(stmt) assert(stmt) diff --git a/src/cpp/pffdtd/config.hpp b/src/cpp/pffdtd/config.hpp index 3039682..fbfe1db 100644 --- a/src/cpp/pffdtd/config.hpp +++ b/src/cpp/pffdtd/config.hpp @@ -6,7 +6,7 @@ #include // flag passed in at compilation (see Makefile) -#if PRECISION == 2 // double +#if PFFDTD_PRECISION == 2 // double typedef double Real; #define REAL_MAX_EXP DBL_MAX_EXP @@ -19,7 +19,7 @@ typedef double Real; #define ADD_D __dadd_rn #define EPS 0.0 -#elif PRECISION == 1 // float with safeguards +#elif PFFDTD_PRECISION == 1 // float with safeguards typedef float Real; #define REAL_MAX_EXP FLT_MAX_EXP @@ -32,5 +32,5 @@ typedef float Real; #define ADD_D __fadd_rn #define EPS 1.19209289e-07 // helps with stability in single #else - #error "PRECISION = 1 (single) or 2 (double)" + #error "PFFDTD_PRECISION = 1 (single) or 2 (double)" #endif diff --git a/src/cpp/pffdtd/exception.hpp b/src/cpp/pffdtd/exception.hpp index 340b06c..99379b2 100644 --- a/src/cpp/pffdtd/exception.hpp +++ b/src/cpp/pffdtd/exception.hpp @@ -15,8 +15,7 @@ template } template -[[noreturn]] auto raisef(fmt::format_string str, Args&&... args) - -> void { +[[noreturn]] auto raisef(fmt::format_string str, Args&&... args) -> void { raise(fmt::format(str, std::forward(args)...)); } diff --git a/src/cpp/pffdtd/hdf.hpp b/src/cpp/pffdtd/hdf.hpp index becc049..7c699b6 100644 --- a/src/cpp/pffdtd/hdf.hpp +++ b/src/cpp/pffdtd/hdf.hpp @@ -3,12 +3,12 @@ #pragma once +#include "pffdtd/assert.hpp" #include "pffdtd/mdspan.hpp" #include "hdf5.h" #include -#include #include #include #include @@ -32,8 +32,7 @@ template inline constexpr auto isStdVector> = true; struct H5FReader { - explicit H5FReader(char const* str) - : _handle{H5Fopen(str, H5F_ACC_RDONLY, H5P_DEFAULT)} {} + explicit H5FReader(char const* str) : _handle{H5Fopen(str, H5F_ACC_RDONLY, H5P_DEFAULT)} {} ~H5FReader() { H5Fclose(_handle); } @@ -71,9 +70,9 @@ struct H5FReader { auto set = H5Dopen(_handle, dataset, H5P_DEFAULT); auto space = H5Dget_space(set); - auto ndims = 1UL; + auto ndims = 1; auto dims = std::array{}; - assert(H5Sget_simple_extent_ndims(space) == ndims); + PFFDTD_ASSERT(H5Sget_simple_extent_ndims(space) == ndims); H5Sget_simple_extent_dims(space, dims.data(), NULL); auto size = ndims == 1 ? dims[0] : dims[0] * dims[1]; @@ -104,8 +103,7 @@ struct H5FReader { } private: - auto checkErrorAndCloseDataset(char const* name, hid_t set, herr_t err) - -> void { + auto checkErrorAndCloseDataset(char const* name, hid_t set, herr_t err) -> void { if (err != 0) { throw std::runtime_error{"dataset read in: " + std::string{name}}; } @@ -120,19 +118,11 @@ struct H5FReader { struct H5FWriter { explicit H5FWriter(std::filesystem::path const& path) - : _handle{H5Fcreate( - path.string().c_str(), - H5F_ACC_TRUNC, - H5P_DEFAULT, - H5P_DEFAULT - )} {} + : _handle{H5Fcreate(path.string().c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)} {} ~H5FWriter() { H5Fclose(_handle); } - auto write( - char const* name, - stdex::mdspan> buf - ) -> void { + auto write(char const* name, stdex::mdspan> buf) -> void { hsize_t dims[2]{ static_cast(buf.extent(0)), static_cast(buf.extent(1)), diff --git a/src/cpp/pffdtd/progress.cpp b/src/cpp/pffdtd/progress.cpp index 697a6d0..72e8b14 100644 --- a/src/cpp/pffdtd/progress.cpp +++ b/src/cpp/pffdtd/progress.cpp @@ -9,9 +9,9 @@ #include #if defined(_WIN32) - #include + #include #else - #include + #include #endif namespace pffdtd { @@ -20,20 +20,20 @@ namespace { [[nodiscard]] auto getConsoleWidth() -> int { #if defined(_WIN32) - auto info = CONSOLE_SCREEN_BUFFER_INFO{}; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) { - return info.srWindow.Right - info.srWindow.Left + 1; - } else { - return 80; - } + auto info = CONSOLE_SCREEN_BUFFER_INFO{}; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) { + return info.srWindow.Right - info.srWindow.Left + 1; + } else { + return 80; + } #else - struct winsize w; - ioctl(0, TIOCGWINSZ, &w); - return w.ws_col; + auto w = winsize{}; + ioctl(0, TIOCGWINSZ, &w); + return w.ws_col; #endif } -} // namespace +} // namespace // hacky print progress (like tqdm).. // N.B. this conflicts with tmux scrolling (stdout needs to flush) @@ -58,8 +58,8 @@ void print_progress( // int ncolsl = 120; // int ncolsp = w.ws_col-ncolsl; - double pcnt = (100.0 * n) / Nt; - int nlines = 6; + double const pcnt = (100.0 * n) / Nt; + int const nlines = 6; if (n > 0) { // back up for (int nl = 0; nl < nlines; nl++) { @@ -89,13 +89,19 @@ void print_progress( fmt::print("."); } } - double est_total = time_elapsed * Nt / n; - - int sec, h_e, m_e, s_e, h_t, m_t, s_t; - sec = (int)time_elapsed; - h_e = (sec / 3600); - m_e = (sec - (3600 * h_e)) / 60; - s_e = (sec - (3600 * h_e) - (m_e * 60)); + double const est_total = time_elapsed * Nt / n; + + int sec = 0; + int h_e = 0; + int m_e = 0; + int s_e = 0; + int h_t = 0; + int m_t = 0; + int s_t = 0; + sec = (int)time_elapsed; + h_e = (sec / 3600); + m_e = (sec - (3600 * h_e)) / 60; + s_e = (sec - (3600 * h_e) - (m_e * 60)); sec = (int)est_total; h_t = (sec / 3600); diff --git a/src/cpp/pffdtd/simulation_3d.cpp b/src/cpp/pffdtd/simulation_3d.cpp index d415435..41f4f37 100644 --- a/src/cpp/pffdtd/simulation_3d.cpp +++ b/src/cpp/pffdtd/simulation_3d.cpp @@ -3,49 +3,39 @@ #include "simulation_3d.hpp" +#include "pffdtd/assert.hpp" + #include #include -#include #include #include #include #include #include +#include namespace { // linear indices to sub-indices in 3d, Nz continguous -void ind2sub3d( - int64_t idx, - int64_t Nx, - int64_t Ny, - int64_t Nz, - int64_t* ix, - int64_t* iy, - int64_t* iz -) { +void ind2sub3d(int64_t idx, int64_t Nx, int64_t Ny, int64_t Nz, int64_t* ix, int64_t* iy, int64_t* iz) { *iz = idx % Nz; *iy = (idx - (*iz)) / Nz % Ny; *ix = ((idx - (*iz)) / Nz - (*iy)) / Ny; - assert(*ix > 0); - assert(*iy > 0); - assert(*iz > 0); - assert(*ix < Nx - 1); - assert(*iy < Ny - 1); - assert(*iz < Nz - 1); + PFFDTD_ASSERT(*ix > 0); + PFFDTD_ASSERT(*iy > 0); + PFFDTD_ASSERT(*iz > 0); + PFFDTD_ASSERT(*ix < Nx - 1); + PFFDTD_ASSERT(*iy < Ny - 1); + PFFDTD_ASSERT(*iz < Nz - 1); } // double check some index inside grid -void check_inside_grid( - int64_t* idx, - int64_t N, - int64_t Nx, - int64_t Ny, - int64_t Nz -) { +void check_inside_grid(int64_t* idx, int64_t N, int64_t Nx, int64_t Ny, int64_t Nz) { for (int64_t i = 0; i < N; i++) { - int64_t iz, iy, ix; + int64_t iz = 0; + int64_t iy = 0; + int64_t ix = 0; ind2sub3d(idx[i], Nx, Ny, Nz, &ix, &iy, &iz); } } @@ -54,42 +44,51 @@ void check_inside_grid( namespace pffdtd { // load the sim data from Python-written HDF5 files -[[nodiscard]] auto loadSimulation3D(std::filesystem::path const& simDir) - -> Simulation3D { +[[nodiscard]] auto loadSimulation3D(std::filesystem::path const& simDir) -> Simulation3D { // local values, to read in and attach to struct at end - int64_t Nx, Ny, Nz; - int64_t Nb, Nbl, Nba; - int64_t Npts; - int64_t Ns, Nr, Nt; - int64_t* bn_ixyz; - int64_t* bnl_ixyz; - int64_t* bna_ixyz; - int8_t* Q_bna; - int64_t *in_ixyz, *out_ixyz, *out_reorder; - bool* adj_bn_bool; - int8_t* K_bn; - uint16_t* adj_bn; // large enough for FCC - uint8_t* bn_mask; - int8_t *mat_bn, *mat_bnl; - double* saf_bn; - Real *ssaf_bn, *ssaf_bnl; - double* in_sigs; - double* u_out; - double l; - double l2; - int8_t fcc_flag; - int8_t NN; - int8_t* Mb; - int8_t Nm; - MatQuad* mat_quads; - Real* mat_beta; // one per material - - double Ts; - bool diff; - - hid_t file; // HDF5 type + int64_t Nx = 0; + int64_t Ny = 0; + int64_t Nz = 0; + int64_t Nb = 0; + int64_t Nbl = 0; + int64_t Nba = 0; + int64_t Npts = 0; + int64_t Ns = 0; + int64_t Nr = 0; + int64_t Nt = 0; + int64_t* bn_ixyz = nullptr; + int64_t* bnl_ixyz = nullptr; + int64_t* bna_ixyz = nullptr; + int8_t* Q_bna = nullptr; + int64_t* in_ixyz = nullptr; + int64_t* out_ixyz = nullptr; + int64_t* out_reorder = nullptr; + bool* adj_bn_bool = nullptr; + int8_t* K_bn = nullptr; + uint16_t* adj_bn = nullptr; // large enough for FCC + uint8_t* bn_mask = nullptr; + int8_t* mat_bn = nullptr; + int8_t* mat_bnl = nullptr; + double* saf_bn = nullptr; + Real* ssaf_bn = nullptr; + Real* ssaf_bnl = nullptr; + double* in_sigs = nullptr; + double* u_out = nullptr; + double l = NAN; + double l2 = NAN; + int8_t fcc_flag = 0; + int8_t NN = 0; + int8_t* Mb = nullptr; + int8_t Nm = 0; + MatQuad* mat_quads = nullptr; + Real* mat_beta = nullptr; // one per material + + double Ts = NAN; + bool diff = false; + + hid_t file = 0; // HDF5 type hsize_t dims[2]; // HDF5 type - int expected_ndims; + int expected_ndims = 0; char dset_str[80]; // char filename[80]; @@ -99,8 +98,9 @@ namespace pffdtd { // //////////////////////////////////////////////////////////////////////// auto filename = simDir / "constants.h5"; - if (not std::filesystem::exists(filename)) - assert(true == false); + if (not std::filesystem::exists(filename)) { + PFFDTD_ASSERT(true == false); + } file = H5Fopen(filename.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -122,35 +122,35 @@ namespace pffdtd { strcpy(dset_str, "fcc_flag"); readH5Constant(file, dset_str, (void*)&fcc_flag, INT8); printf("fcc_flag=%d\n", fcc_flag); - assert((fcc_flag >= 0) && (fcc_flag <= 2)); + PFFDTD_ASSERT((fcc_flag >= 0) && (fcc_flag <= 2)); if (H5Fclose(file) != 0) { fmt::println("error closing file {}", filename.string()); - assert(true == false); - } else + PFFDTD_ASSERT(true == false); + } else { fmt::println("closed file {}", filename.string()); + } if (fcc_flag > 0) { // FCC (1 is CPU-based, 2 is CPU or GPU) - assert(l2 <= 1.0); - assert(l <= 1.0); + PFFDTD_ASSERT(l2 <= 1.0); + PFFDTD_ASSERT(l <= 1.0); NN = 12; } else { // simple Cartesian - assert(l2 <= 1.0 / 3.0); - assert(l <= sqrt(1.0 / 3.0)); + PFFDTD_ASSERT(l2 <= 1.0 / 3.0); + PFFDTD_ASSERT(l <= sqrt(1.0 / 3.0)); NN = 6; } // calculate some update coefficients - double lfac = (fcc_flag > 0) ? 0.25 : 1.0; // laplacian factor - double dsl2 - = (1.0 + EPS) * lfac * l2; // scale for stability (EPS in fdtd_common.hpp) - double da1 = (2.0 - dsl2 * NN); // scaling for stability in single - double da2 = lfac * l2; + double const lfac = (fcc_flag > 0) ? 0.25 : 1.0; // laplacian factor + double const dsl2 = (1.0 + EPS) * lfac * l2; // scale for stability (EPS in fdtd_common.hpp) + double const da1 = (2.0 - dsl2 * NN); // scaling for stability in single + double const da2 = lfac * l2; // Real is defined in fdtd_common.hpp (float or double) - Real a1 = da1; - Real a2 = da2; - Real sl2 = dsl2; - Real lo2 = 0.5 * l; + Real const a1 = da1; + Real const a2 = da2; + Real const sl2 = dsl2; + Real const lo2 = 0.5 * l; printf("a2 (double): %.16g\n", da2); printf("a2 (Real): %.16g\n", a2); @@ -168,8 +168,9 @@ namespace pffdtd { // //////////////////////////////////////////////////////////////////////// filename = simDir / "vox_out.h5"; - if (not std::filesystem::exists(filename)) - assert(true == false); + if (not std::filesystem::exists(filename)) { + PFFDTD_ASSERT(true == false); + } file = H5Fopen(filename.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -201,7 +202,7 @@ namespace pffdtd { strcpy(dset_str, "bn_ixyz"); expected_ndims = 1; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&bn_ixyz, INT64); - assert((int64_t)dims[0] == Nb); + PFFDTD_ASSERT((int64_t)dims[0] == Nb); ////////////////// // adj_bn dataset @@ -209,8 +210,8 @@ namespace pffdtd { strcpy(dset_str, "adj_bn"); expected_ndims = 2; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&adj_bn_bool, BOOL); - assert((int64_t)dims[0] == Nb); - assert(dims[1] == (hsize_t)NN); + PFFDTD_ASSERT((int64_t)dims[0] == Nb); + PFFDTD_ASSERT(dims[1] == (hsize_t)NN); ////////////////// // mat_bn dataset @@ -218,7 +219,7 @@ namespace pffdtd { strcpy(dset_str, "mat_bn"); expected_ndims = 1; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&mat_bn, INT8); - assert((int64_t)dims[0] == Nb); + PFFDTD_ASSERT((int64_t)dims[0] == Nb); ////////////////// // saf_bn dataset @@ -226,23 +227,24 @@ namespace pffdtd { strcpy(dset_str, "saf_bn"); expected_ndims = 1; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&saf_bn, FLOAT64); - assert((int64_t)dims[0] == Nb); + PFFDTD_ASSERT((int64_t)dims[0] == Nb); allocate_zeros((void**)&ssaf_bn, Nb * sizeof(Real)); for (int64_t i = 0; i < Nb; i++) { - if (fcc_flag > 0) - ssaf_bn[i] - = (Real)(0.5 / sqrt(2.0)) * saf_bn[i]; // rescale for S*h/V and cast - else + if (fcc_flag > 0) { + ssaf_bn[i] = (Real)(0.5 / std::numbers::sqrt2) * saf_bn[i]; // rescale for S*h/V and cast + } else { ssaf_bn[i] = (Real)saf_bn[i]; // just cast + } } free(saf_bn); if (H5Fclose(file) != 0) { fmt::println("error closing file {}", filename.string()); - assert(true == false); - } else + PFFDTD_ASSERT(true == false); + } else { fmt::println("closed file {}", filename.string()); + } //////////////////////////////////////////////////////////////////////// // @@ -250,8 +252,9 @@ namespace pffdtd { // //////////////////////////////////////////////////////////////////////// filename = simDir / "signals.h5"; - if (not std::filesystem::exists(filename)) - assert(true == false); + if (not std::filesystem::exists(filename)) { + PFFDTD_ASSERT(true == false); + } file = H5Fopen(filename.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -276,7 +279,7 @@ namespace pffdtd { strcpy(dset_str, "diff"); readH5Constant(file, dset_str, (void*)&diff, BOOL); - printf("diff=%d\n", diff); + printf("diff=%d\n", static_cast(diff)); ////////////////// // in_ixyz dataset @@ -284,7 +287,7 @@ namespace pffdtd { strcpy(dset_str, "in_ixyz"); expected_ndims = 1; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&in_ixyz, INT64); - assert((int64_t)dims[0] == Ns); + PFFDTD_ASSERT((int64_t)dims[0] == Ns); ////////////////// // out_ixyz dataset @@ -292,19 +295,12 @@ namespace pffdtd { strcpy(dset_str, "out_ixyz"); expected_ndims = 1; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&out_ixyz, INT64); - assert((int64_t)dims[0] == Nr); + PFFDTD_ASSERT((int64_t)dims[0] == Nr); strcpy(dset_str, "out_reorder"); expected_ndims = 1; - readH5Dataset( - file, - dset_str, - expected_ndims, - dims, - (void**)&out_reorder, - INT64 - ); - assert((int64_t)dims[0] == Nr); + readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&out_reorder, INT64); + PFFDTD_ASSERT((int64_t)dims[0] == Nr); ////////////////// // in_sigs dataset @@ -312,18 +308,20 @@ namespace pffdtd { strcpy(dset_str, "in_sigs"); expected_ndims = 2; readH5Dataset(file, dset_str, expected_ndims, dims, (void**)&in_sigs, FLOAT64); - assert((int64_t)dims[0] == Ns); - assert((int64_t)dims[1] == Nt); + PFFDTD_ASSERT((int64_t)dims[0] == Ns); + PFFDTD_ASSERT((int64_t)dims[1] == Nt); if (H5Fclose(file) != 0) { fmt::println("error closing file {}", filename.string()); - assert(true == false); - } else + PFFDTD_ASSERT(true == false); + } else { fmt::println("closed file {}", filename.string()); + } // not recommended to run single without differentiating input - if (sizeof(Real) == 4) - assert(diff); + if (sizeof(Real) == 4) { + PFFDTD_ASSERT(diff); + } //////////////////////////////////////////////////////////////////////// // @@ -331,8 +329,9 @@ namespace pffdtd { // //////////////////////////////////////////////////////////////////////// filename = simDir / "materials.h5"; - if (not std::filesystem::exists(filename)) - assert(true == false); + if (not std::filesystem::exists(filename)) { + PFFDTD_ASSERT(true == false); + } file = H5Fopen(filename.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -344,7 +343,7 @@ namespace pffdtd { readH5Constant(file, dset_str, (void*)&Nm, INT8); printf("Nm=%d\n", Nm); - assert(Nm <= MNm); + PFFDTD_ASSERT(Nm <= MNm); strcpy(dset_str, "Mb"); expected_ndims = 1; @@ -357,42 +356,39 @@ namespace pffdtd { ////////////////// // DEF (RLC) datasets ////////////////// - allocate_zeros( - (void**)&mat_quads, - static_cast(Nm * MMb) * sizeof(MatQuad) - ); // initalises to zero + allocate_zeros((void**)&mat_quads, static_cast(Nm * MMb) * sizeof(MatQuad)); allocate_zeros((void**)&mat_beta, Nm * sizeof(Real)); for (int8_t i = 0; i < Nm; i++) { - double* DEF; // for one material + double* DEF = nullptr; // for one material // sprintf(dset_str, "mat_%02d_DEF", i); auto id = fmt::format("mat_{:02d}_DEF", i); expected_ndims = 2; readH5Dataset(file, id.data(), expected_ndims, dims, (void**)&DEF, FLOAT64); - assert((int8_t)dims[0] == Mb[i]); - assert((int8_t)dims[1] == 3); - assert(Mb[i] <= MMb); + PFFDTD_ASSERT((int8_t)dims[0] == Mb[i]); + PFFDTD_ASSERT((int8_t)dims[1] == 3); + PFFDTD_ASSERT(Mb[i] <= MMb); for (int8_t j = 0; j < Mb[i]; j++) { - double D = DEF[j * 3 + 0]; - double E = DEF[j * 3 + 1]; - double F = DEF[j * 3 + 2]; + double const D = DEF[j * 3 + 0]; + double const E = DEF[j * 3 + 1]; + double const F = DEF[j * 3 + 2]; printf("DEF[%d,%d]=[%.16g, %.16g, %.16g] \n", i, j, D, E, F); // see 2016 ISMRA paper - double Dh = D / Ts; - double Eh = E; - double Fh = F * Ts; - - double b = 1.0 / (2.0 * Dh + Eh + 0.5 * Fh); - double bd = b * (2.0 * Dh - Eh - 0.5 * Fh); - double bDh = b * Dh; - double bFh = b * Fh; - assert(not std::isinf(b)); - assert(not std::isnan(b)); - assert(not std::isinf(bd)); - assert(not std::isnan(bd)); - - int32_t mij = (int32_t)MMb * i + j; + double const Dh = D / Ts; + double const Eh = E; + double const Fh = F * Ts; + + double const b = 1.0 / (2.0 * Dh + Eh + 0.5 * Fh); + double const bd = b * (2.0 * Dh - Eh - 0.5 * Fh); + double const bDh = b * Dh; + double const bFh = b * Fh; + PFFDTD_ASSERT(not std::isinf(b)); + PFFDTD_ASSERT(not std::isnan(b)); + PFFDTD_ASSERT(not std::isinf(bd)); + PFFDTD_ASSERT(not std::isnan(bd)); + + int32_t const mij = (int32_t)MMb * i + j; mat_quads[mij].b = (Real)b; mat_quads[mij].bd = (Real)bd; mat_quads[mij].bDh = (Real)bDh; @@ -404,9 +400,10 @@ namespace pffdtd { if (H5Fclose(file) != 0) { fmt::println("error closing file {}", filename.string()); - assert(true == false); - } else + PFFDTD_ASSERT(true == false); + } else { fmt::println("closed file {}", filename.string()); + } //////////////////////////////////////////////////////////////////////// // @@ -427,13 +424,14 @@ namespace pffdtd { bool at_least_one_not_adj = false; bool all_not_adj = true; for (int8_t j = 0; j < NN; j++) { - bool adj = adj_bn_bool[i * NN + j]; + bool const adj = adj_bn_bool[i * NN + j]; at_least_one_not_adj |= !adj; all_not_adj &= !adj; } - assert(at_least_one_not_adj); - if (all_not_adj) - assert(mat_bn[i] == -1); + PFFDTD_ASSERT(at_least_one_not_adj); + if (all_not_adj) { + PFFDTD_ASSERT(mat_bn[i] == -1); + } } printf("checked adj_bn against mat_bn.\n"); @@ -451,7 +449,7 @@ namespace pffdtd { for (int64_t i = 0; i < Nb; i++) { for (int8_t j = 0; j < NN; j++) { // avoids race conditions - assert(GET_BIT(adj_bn[i], j) == adj_bn_bool[i * NN + j]); + PFFDTD_ASSERT(GET_BIT(adj_bn[i], j) == adj_bn_bool[i * NN + j]); } } printf("adj_bn double checked\n"); @@ -474,29 +472,30 @@ namespace pffdtd { // bit-pack and check bn_mask ////////////////// // make compressed bit-mask - int64_t Nbm = (Npts - 1) / 8 + 1; + int64_t const Nbm = (Npts - 1) / 8 + 1; allocate_zeros((void**)&bn_mask, Nbm); // one bit per for (int64_t i = 0; i < Nb; i++) { - int64_t ii = bn_ixyz[i]; + int64_t const ii = bn_ixyz[i]; SET_BIT(bn_mask[ii >> 3], ii % 8); } // create bn_mask_raw to double check - bool* bn_mask_raw; + bool* bn_mask_raw = nullptr; allocate_zeros((void**)&bn_mask_raw, Npts * sizeof(bool)); for (int64_t i = 0; i < Nb; i++) { - int64_t ii = bn_ixyz[i]; - assert(ii < Npts); + int64_t const ii = bn_ixyz[i]; + PFFDTD_ASSERT(ii < Npts); bn_mask_raw[ii] = true; } printf("bn_mask_raw filled\n"); for (int64_t j = 0; j < Nbm; j++) { for (int64_t q = 0; q < 8; q++) { // avoid race conditions - int64_t i = j * 8 + q; - if (i < Npts) - assert(GET_BIT(bn_mask[i >> 3], i % 8) == bn_mask_raw[i]); + int64_t const i = j * 8 + q; + if (i < Npts) { + PFFDTD_ASSERT(GET_BIT(bn_mask[i >> 3], i % 8) == bn_mask_raw[i]); + } } } printf("bn_mask double checked\n"); @@ -505,7 +504,7 @@ namespace pffdtd { // count Nbl Nbl = 0; for (int64_t i = 0; i < Nb; i++) { - Nbl += mat_bn[i] >= 0; + Nbl += static_cast(mat_bn[i] >= 0); } printf("Nbl = %ld\n", static_cast(Nbl)); allocate_zeros((void**)&mat_bnl, Nbl * sizeof(int8_t)); @@ -521,7 +520,7 @@ namespace pffdtd { j++; } } - assert(j == Nbl); + PFFDTD_ASSERT(j == Nbl); } free(mat_bn); free(ssaf_bn); @@ -529,12 +528,12 @@ namespace pffdtd { printf("separated non-rigid bn\n"); // ABC ndoes - int64_t Nyf; - Nyf = (fcc_flag == 2) ? 2 * (Ny - 1) - : Ny; // full Ny dim, taking into account FCC fold - Nba = 2 * (Nx * Nyf + Nx * Nz + Nyf * Nz) - 12 * (Nx + Nyf + Nz) + 56; - if (fcc_flag > 0) + int64_t Nyf = 0; + Nyf = (fcc_flag == 2) ? 2 * (Ny - 1) : Ny; // full Ny dim, taking into account FCC fold + Nba = 2 * (Nx * Nyf + Nx * Nz + Nyf * Nz) - 12 * (Nx + Nyf + Nz) + 56; + if (fcc_flag > 0) { Nba /= 2; + } allocate_zeros((void**)&bna_ixyz, Nba * sizeof(int64_t)); allocate_zeros((void**)&Q_bna, Nba * sizeof(int8_t)); @@ -544,17 +543,17 @@ namespace pffdtd { for (int64_t iy = 1; iy < Nyf - 1; iy++) { for (int64_t iz = 1; iz < Nz - 1; iz++) { - if ((fcc_flag > 0) && (ix + iy + iz) % 2 == 1) + if ((fcc_flag > 0) && (ix + iy + iz) % 2 == 1) { continue; + } int8_t Q = 0; - Q += ((ix == 1) || (ix == Nx - 2)); - Q += ((iy == 1) || (iy == Nyf - 2)); - Q += ((iz == 1) || (iz == Nz - 2)); + Q += static_cast((ix == 1) || (ix == Nx - 2)); + Q += static_cast((iy == 1) || (iy == Nyf - 2)); + Q += static_cast((iz == 1) || (iz == Nz - 2)); if (Q > 0) { if ((fcc_flag == 2) && (iy >= Nyf / 2)) { - bna_ixyz[ii] = ix * Nz * Ny + (Nyf - iy - 1) * Nz - + iz; // index on folded grid + bna_ixyz[ii] = ix * Nz * Ny + (Nyf - iy - 1) * Nz + iz; // index on folded grid } else { bna_ixyz[ii] = ix * Nz * Ny + iy * Nz + iz; } @@ -564,16 +563,16 @@ namespace pffdtd { } } } - assert(ii == Nba); + PFFDTD_ASSERT(ii == Nba); printf("ABC nodes\n"); if (fcc_flag == 2) { // need to sort bna_ixyz - int64_t* bna_sort_keys; + int64_t* bna_sort_keys = nullptr; allocate_zeros((void**)&bna_sort_keys, Nba * sizeof(int64_t)); sort_keys(bna_ixyz, bna_sort_keys, Nba); // now sort corresponding Q_bna array - int8_t* Q_bna_sorted; - int8_t* Q_bna_unsorted; + int8_t* Q_bna_sorted = nullptr; + int8_t* Q_bna_unsorted = nullptr; allocate_zeros((void**)&Q_bna_sorted, Nba * sizeof(int8_t)); // swap pointers Q_bna_unsorted = Q_bna; @@ -660,22 +659,16 @@ void freeSimulation3D(Simulation3D& sim) { } // read HDF5 files -void readH5Dataset( - hid_t file, - char* dset_str, - int ndims, - hsize_t* dims, - void** out_array, - DataType t -) { - hid_t dset, dspace; - uint64_t N = 0; +void readH5Dataset(hid_t file, char* dset_str, int ndims, hsize_t* dims, void** out_array, DataType t) { + hid_t dset = 0; + hid_t dspace = 0; + uint64_t N = 0; // herr_t status; dset = H5Dopen(file, dset_str, H5P_DEFAULT); dspace = H5Dget_space(dset); - assert(H5Sget_simple_extent_ndims(dspace) == ndims); - H5Sget_simple_extent_dims(dspace, dims, NULL); + PFFDTD_ASSERT(H5Sget_simple_extent_ndims(dspace) == ndims); + H5Sget_simple_extent_dims(dspace, dims, nullptr); if (ndims == 1) { // printf("size dim 0 = %llu\n",dims[0]); N = dims[0]; @@ -684,7 +677,7 @@ void readH5Dataset( // printf("size dim 1 = %llu\n",dims[1]); N = dims[0] * dims[1]; } else { - assert(true == false); + PFFDTD_ASSERT(true == false); } switch (t) { case FLOAT64: *out_array = (double*)malloc(N * sizeof(double)); break; @@ -692,73 +685,29 @@ void readH5Dataset( case INT64: *out_array = (int64_t*)malloc(N * sizeof(int64_t)); break; case INT8: *out_array = (int8_t*)malloc(N * sizeof(int8_t)); break; case BOOL: *out_array = (bool*)malloc(N * sizeof(bool)); break; - default: assert(true == false); + default: PFFDTD_ASSERT(true == false); } - if (*out_array == NULL) { + if (*out_array == nullptr) { printf("Memory allocation failed"); - assert(true == false); // to break + PFFDTD_ASSERT(true == false); // to break } - herr_t status; + herr_t status = 0; switch (t) { - case FLOAT64: - status = H5Dread( - dset, - H5T_NATIVE_DOUBLE, - H5S_ALL, - H5S_ALL, - H5P_DEFAULT, - *out_array - ); - break; - case FLOAT32: - status = H5Dread( - dset, - H5T_NATIVE_FLOAT, - H5S_ALL, - H5S_ALL, - H5P_DEFAULT, - *out_array - ); - break; - case INT64: - status = H5Dread( - dset, - H5T_NATIVE_INT64, - H5S_ALL, - H5S_ALL, - H5P_DEFAULT, - *out_array - ); - break; - case INT8: - case BOOL: // bool read in as INT8 - status = H5Dread( - dset, - H5T_NATIVE_INT8, - H5S_ALL, - H5S_ALL, - H5P_DEFAULT, - *out_array - ); - status = H5Dread( - dset, - H5T_NATIVE_INT8, - H5S_ALL, - H5S_ALL, - H5P_DEFAULT, - *out_array - ); - break; - default: assert(true == false); + case FLOAT64: status = H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, *out_array); break; + case FLOAT32: status = H5Dread(dset, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, *out_array); break; + case INT64: status = H5Dread(dset, H5T_NATIVE_INT64, H5S_ALL, H5S_ALL, H5P_DEFAULT, *out_array); break; + case INT8: // bool read in as INT8 + case BOOL: status = H5Dread(dset, H5T_NATIVE_INT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, *out_array); break; + default: PFFDTD_ASSERT(true == false); } if (status != 0) { printf("error reading dataset: %s\n", dset_str); - assert(true == false); + PFFDTD_ASSERT(true == false); } if (H5Dclose(dset) != 0) { printf("error closing dataset: %s\n", dset_str); - assert(true == false); + PFFDTD_ASSERT(true == false); } else { printf("read and closed dataset: %s\n", dset_str); } @@ -766,36 +715,28 @@ void readH5Dataset( // read scalars from HDF5 datasets void readH5Constant(hid_t file, char* dset_str, void* out, DataType t) { - hid_t dset, dspace; + hid_t dset = 0; + hid_t dspace = 0; dset = H5Dopen(file, dset_str, H5P_DEFAULT); dspace = H5Dget_space(dset); - assert(H5Sget_simple_extent_ndims(dspace) == 0); - herr_t status; + PFFDTD_ASSERT(H5Sget_simple_extent_ndims(dspace) == 0); + herr_t status = 0; switch (t) { - case FLOAT64: - status - = H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); - break; - case INT64: - status - = H5Dread(dset, H5T_NATIVE_INT64, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); - break; + case FLOAT64: status = H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); break; + case INT64: status = H5Dread(dset, H5T_NATIVE_INT64, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); break; case INT8: - case BOOL: - status = H5Dread(dset, H5T_NATIVE_INT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); - status = H5Dread(dset, H5T_NATIVE_INT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); - break; - default: assert(true == false); + case BOOL: status = H5Dread(dset, H5T_NATIVE_INT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, out); break; + default: PFFDTD_ASSERT(true == false); } if (status != 0) { printf("error reading dataset: %s\n", dset_str); - assert(true == false); + PFFDTD_ASSERT(true == false); } if (H5Dclose(dset) != 0) { printf("error closing dataset: %s\n", dset_str); - assert(true == false); + PFFDTD_ASSERT(true == false); } else { printf("read constant: %s\n", dset_str); } @@ -803,8 +744,8 @@ void readH5Constant(hid_t file, char* dset_str, void* out, DataType t) { // print last samples of simulation (for correctness checking..) void printLastSample(Simulation3D& sim) { - int64_t Nt = sim.Nt; - int64_t Nr = sim.Nr; + int64_t const Nt = sim.Nt; + int64_t const Nr = sim.Nr; double* u_out = sim.u_out; int64_t* out_reorder = sim.out_reorder; // print last samples @@ -819,9 +760,9 @@ void printLastSample(Simulation3D& sim) { // scale input to be in middle of floating-point range void scaleInput(Simulation3D& sim) { - double* in_sigs = sim.in_sigs; - int64_t Nt = sim.Nt; - int64_t Ns = sim.Ns; + double* in_sigs = sim.in_sigs; + int64_t const Nt = sim.Nt; + int64_t const Ns = sim.Ns; // normalise input signals (and save gain) double max_in = 0.0; @@ -830,12 +771,12 @@ void scaleInput(Simulation3D& sim) { max_in = std::max(max_in, fabs(in_sigs[ns * Nt + n])); } } - double aexp = 0.5; // normalise to middle power of two - int32_t pow2 = (int32_t)round(aexp * REAL_MAX_EXP + (1 - aexp) * REAL_MIN_EXP); + double const aexp = 0.5; // normalise to middle power of two + auto pow2 = (int32_t)round(aexp * REAL_MAX_EXP + (1 - aexp) * REAL_MIN_EXP); // int32_t pow2 = 0; //normalise to one - double norm1 = pow(2.0, pow2); - double inv_infac = norm1 / max_in; - double infac = 1.0 / inv_infac; + double const norm1 = pow(2.0, pow2); + double const inv_infac = norm1 / max_in; + double const infac = 1.0 / inv_infac; printf( "max_in = %.16e, pow2 = %d, norm1 = %.16e, inv_infac = %.16e, infac = " @@ -860,14 +801,12 @@ void scaleInput(Simulation3D& sim) { // undo that scaling void rescaleOutput(Simulation3D& sim) { - int64_t Nt = sim.Nt; - int64_t Nr = sim.Nr; - double infac = sim.infac; - double* u_out = sim.u_out; - - std::transform(u_out, u_out + Nr * Nt, u_out, [infac](auto sample) { - return sample * infac; - }); + int64_t const Nt = sim.Nt; + int64_t const Nr = sim.Nr; + double infac = sim.infac; + double* u_out = sim.u_out; + + std::transform(u_out, u_out + Nr * Nt, u_out, [infac](auto sample) { return sample * infac; }); } void writeOutputs(Simulation3D& sim, std::filesystem::path const& simDir) { @@ -883,7 +822,7 @@ void writeOutputs(Simulation3D& sim, std::filesystem::path const& simDir) { } } - auto writer = pffdtd::H5FWriter{simDir / "sim_outs.h5"}; + auto writer = H5FWriter{simDir / "sim_outs.h5"}; writer.write("u_out", u_out); std::puts("wrote output dataset"); } diff --git a/src/cpp/pffdtd/simulation_3d.hpp b/src/cpp/pffdtd/simulation_3d.hpp index d7dd259..0ac1d94 100644 --- a/src/cpp/pffdtd/simulation_3d.hpp +++ b/src/cpp/pffdtd/simulation_3d.hpp @@ -20,70 +20,63 @@ namespace pffdtd { // see python code and 2016 ISMRA paper +template struct MatQuad { - Real b; // b - Real bd; // b*d - Real bDh; // b*D-hat - Real bFh; // b*F-hat + Float b; // b + Float bd; // b*d + Float bDh; // b*D-hat + Float bFh; // b*F-hat }; // main sim data, on host struct Simulation3D { - int64_t* bn_ixyz; // boundary node indices - int64_t* bnl_ixyz; // lossy boundary node indices - int64_t* bna_ixyz; // absorbing boundary node indices - int8_t* Q_bna; // integer for ABCs (wall 1,edge 2,corner 3) - int64_t* in_ixyz; // input points - int64_t* out_ixyz; // output points - int64_t* out_reorder; // ordering for outputs point for final print/save - uint16_t* adj_bn; // nearest-neighbour adjancencies for all boundary nodes - Real* ssaf_bnl; // surface area corrections (with extra volume scaling) - uint8_t* bn_mask; // bit mask for bounday nodes - int8_t* mat_bnl; // material indices for lossy boundary nodes - int8_t* K_bn; // number of adjacent neighbours, boundary nodesa - double* in_sigs; // input signals - double* u_out; // for output signals - int64_t Ns; // number of input grid points - int64_t Nr; // number of output grid points - int64_t Nt; // number of samples simulation - int64_t Npts; // number of Cartesian grid points - int64_t Nx; // x-dim (non-continguous) - int64_t Ny; // y-dim - int64_t Nz; // z-dim (continguous) - int64_t Nb; // number of boundary nodes - int64_t Nbl; // number of lossy boundary nodes - int64_t Nba; // number of ABC nodes - double l; // Courant number (CFL) - double l2; // CFL number squared - int8_t fcc_flag; // boolean for FCC - int8_t NN; // integer, neareast neighbours - int8_t Nm; // number of materials used - int8_t* Mb; // number of branches per material - struct MatQuad* mat_quads; // RLC coefficients (essentially) - Real* mat_beta; // part of FD-boundaries one per material - double infac; // rescaling of input (for numerical reason) - Real sl2; // scaled l2 (for single precision) - Real lo2; // 0.5*l - Real a2; // update stencil coefficient - Real a1; // update stencil coefficient + int64_t* bn_ixyz; // boundary node indices + int64_t* bnl_ixyz; // lossy boundary node indices + int64_t* bna_ixyz; // absorbing boundary node indices + int8_t* Q_bna; // integer for ABCs (wall 1,edge 2,corner 3) + int64_t* in_ixyz; // input points + int64_t* out_ixyz; // output points + int64_t* out_reorder; // ordering for outputs point for final print/save + uint16_t* adj_bn; // nearest-neighbour adjancencies for all boundary nodes + Real* ssaf_bnl; // surface area corrections (with extra volume scaling) + uint8_t* bn_mask; // bit mask for bounday nodes + int8_t* mat_bnl; // material indices for lossy boundary nodes + int8_t* K_bn; // number of adjacent neighbours, boundary nodesa + double* in_sigs; // input signals + double* u_out; // for output signals + int64_t Ns; // number of input grid points + int64_t Nr; // number of output grid points + int64_t Nt; // number of samples simulation + int64_t Npts; // number of Cartesian grid points + int64_t Nx; // x-dim (non-continguous) + int64_t Ny; // y-dim + int64_t Nz; // z-dim (continguous) + int64_t Nb; // number of boundary nodes + int64_t Nbl; // number of lossy boundary nodes + int64_t Nba; // number of ABC nodes + double l; // Courant number (CFL) + double l2; // CFL number squared + int8_t fcc_flag; // boolean for FCC + int8_t NN; // integer, neareast neighbours + int8_t Nm; // number of materials used + int8_t* Mb; // number of branches per material + MatQuad* mat_quads; // RLC coefficients (essentially) + Real* mat_beta; // part of FD-boundaries one per material + double infac; // rescaling of input (for numerical reason) + Real sl2; // scaled l2 (for single precision) + Real lo2; // 0.5*l + Real a2; // update stencil coefficient + Real a1; // update stencil coefficient }; -[[nodiscard]] auto loadSimulation3D(std::filesystem::path const& simDir) - -> Simulation3D; +[[nodiscard]] auto loadSimulation3D(std::filesystem::path const& simDir) -> Simulation3D; void freeSimulation3D(Simulation3D& sim); void printLastSample(Simulation3D& sim); void scaleInput(Simulation3D& sim); void rescaleOutput(Simulation3D& sim); void writeOutputs(Simulation3D& sim, std::filesystem::path const& simDir); -void readH5Dataset( - hid_t file, - char* dset_str, - int ndims, - hsize_t* dims, - void** data_array, - DataType t -); +void readH5Dataset(hid_t file, char* dset_str, int ndims, hsize_t* dims, void** out_array, DataType t); void readH5Constant(hid_t file, char* dset_str, void* out, DataType t); } // namespace pffdtd diff --git a/src/cpp/pffdtd/sycl.hpp b/src/cpp/pffdtd/sycl.hpp index 10ce2a3..438b5c6 100644 --- a/src/cpp/pffdtd/sycl.hpp +++ b/src/cpp/pffdtd/sycl.hpp @@ -45,9 +45,7 @@ inline auto summary(sycl::device dev) -> void { template [[nodiscard]] auto getPtr(Accessor&& a) -> auto* { - return std::forward(a) - .template get_multi_ptr() - .get(); + return std::forward(a).template get_multi_ptr().get(); } } // namespace pffdtd diff --git a/src/cpp/pffdtd/utility.cpp b/src/cpp/pffdtd/utility.cpp index 04d2749..d52ac1d 100644 --- a/src/cpp/pffdtd/utility.cpp +++ b/src/cpp/pffdtd/utility.cpp @@ -8,38 +8,41 @@ #include #include +namespace pffdtd { + // malloc check malloc, and initialise to zero // hard stop program if failed void allocate_zeros(void** arr, uint64_t Nbytes) { *arr = malloc(Nbytes); - if (*arr == NULL) { - pffdtd::raise(); + if (*arr == nullptr) { + raise(); } // initialise to zero std::memset(*arr, 0, (size_t)Nbytes); } -// for sorting int64 arrays and returning keys -struct sort_int64_struct { - int64_t val; - int64_t idx; -}; - -// comparator with indice keys (for FCC ABC nodes) -static int cmpfunc_int64_keys(void const* a, void const* b) { - if ((*(sort_int64_struct const*)a).val < (*(sort_int64_struct const*)b).val) - return -1; - if ((*(sort_int64_struct const*)a).val > (*(sort_int64_struct const*)b).val) - return 1; - return 0; -} - // sort and return indices void sort_keys(int64_t* val_arr, int64_t* key_arr, int64_t N) { - sort_int64_struct* struct_arr; - struct_arr = (sort_int64_struct*)malloc(N * sizeof(sort_int64_struct)); - if (struct_arr == NULL) { - pffdtd::raise(); + // for sorting int64 arrays and returning keys + struct sort_int64_struct { + int64_t val; + int64_t idx; + }; + + // comparator with indice keys (for FCC ABC nodes) + static constexpr auto cmpfunc_int64_keys = [](void const* a, void const* b) -> int { + if ((*(sort_int64_struct const*)a).val < (*(sort_int64_struct const*)b).val) { + return -1; + } + if ((*(sort_int64_struct const*)a).val > (*(sort_int64_struct const*)b).val) { + return 1; + } + return 0; + }; + + auto* struct_arr = (sort_int64_struct*)malloc(N * sizeof(sort_int64_struct)); + if (struct_arr == nullptr) { + raise(); } for (int64_t i = 0; i < N; i++) { struct_arr[i].val = val_arr[i]; @@ -52,3 +55,5 @@ void sort_keys(int64_t* val_arr, int64_t* key_arr, int64_t N) { } free(struct_arr); } + +} // namespace pffdtd diff --git a/src/cpp/pffdtd/utility.hpp b/src/cpp/pffdtd/utility.hpp index 7d1ae42..7e6f956 100644 --- a/src/cpp/pffdtd/utility.hpp +++ b/src/cpp/pffdtd/utility.hpp @@ -6,12 +6,13 @@ #include #ifndef DIV_CEIL - #define DIV_CEIL(x, y) (((x) + (y)-1) / (y)) // this works for x≥0 and y>0 + #define DIV_CEIL(x, y) (((x) + (y) - 1) / (y)) // this works for x≥0 and y>0 #endif -#define GET_BIT(var, pos) (((var) >> (pos)) & 1) -#define SET_BIT(var, pos) ((var) |= (1ULL << (pos))) -#define SET_BIT_VAL(var, pos, val) \ - ((var) = ((var) & ~(1ULL << (pos))) | ((val) << (pos))) +#define GET_BIT(var, pos) (((var) >> (pos)) & 1) +#define SET_BIT(var, pos) ((var) |= (1ULL << (pos))) +#define SET_BIT_VAL(var, pos, val) ((var) = ((var) & ~(1ULL << (pos))) | ((val) << (pos))) +namespace pffdtd { void allocate_zeros(void** arr, uint64_t Nbytes); void sort_keys(int64_t* val_arr, int64_t* key_arr, int64_t N); +} // namespace pffdtd diff --git a/src/python/pffdtd/absorption/air.py b/src/python/pffdtd/absorption/air.py index 3cd0f7f..e976636 100644 --- a/src/python/pffdtd/absorption/air.py +++ b/src/python/pffdtd/absorption/air.py @@ -1,9 +1,11 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2021 Brian Hamilton +from dataclasses import dataclass import numba as nb import numpy as np from numpy import exp, pi, cos, sqrt, log +from numpy.typing import ArrayLike from scipy.fft import dct, idct # default type2 from scipy.fft import rfft, irfft from tqdm import tqdm @@ -11,7 +13,36 @@ from pffdtd.geometry.math import iceil, iround -def air_absorption(frequencies, temperature_celsius, rel_humidity_pnct, pressure_atmospheric_kPa=101.325): +@dataclass +class AirAbsorption: + gamma_p: np.float64 + gamma: np.float64 + etaO: np.float64 + eta: np.float64 + almN: np.float64 + almO: np.float64 + c: np.float64 + frO: np.float64 + frN: np.float64 + + # frequency-dependent coefficeints in Np/m or dB/m + absVibN_dB: np.ndarray + absVibO_dB: np.ndarray + absClRo_dB: np.ndarray + absfull_dB: np.ndarray + + absVibN_Np: np.ndarray + absVibO_Np: np.ndarray + absClRo_Np: np.ndarray + absfull_Np: np.ndarray + + +def air_absorption( + frequencies: ArrayLike, + temperature_celsius: float, + rel_humidity_pnct: float, + pressure_atmospheric_kPa: float = 101.325 +) -> AirAbsorption: """This is an implementation of formulae in the ISO9613-1 standard for air absorption """ assert pressure_atmospheric_kPa <= 200 @@ -78,7 +109,8 @@ def air_absorption(frequencies, temperature_celsius, rel_humidity_pnct, pressure frN = p * Tr**(-0.5) * (9 + 280 * h * np.exp(-4.17 * (Tr**(-1/3) - 1))) # (5) - absfull1 = 8.686*f2*(1.84e-11 * np.sqrt(Tr)/p + Tr**-2.5 * (0.01275*(np.exp(-2239.1/Tk)/(frO + f2/frO)) + 0.1068*(np.exp(-3352.0/Tk)/(frN + f2/frN)))) + absfull1 = 8.686*f2*(1.84e-11 * np.sqrt(Tr)/p + Tr**-2.5 * (0.01275*( + np.exp(-2239.1/Tk)/(frO + f2/frO)) + 0.1068*(np.exp(-3352.0/Tk)/(frN + f2/frN)))) # (A.2) absClRo = 1.6e-10*np.sqrt(Tr)*f2/p @@ -99,28 +131,26 @@ def air_absorption(frequencies, temperature_celsius, rel_humidity_pnct, pressure etaO = almO*(c/pi2/frO)*np.log(10)/20 # return a dictionary of different constants - return_dict = {} - return_dict['gamma_p'] = etaO/c - return_dict['gamma'] = eta/c - return_dict['etaO'] = etaO - return_dict['eta'] = eta - return_dict['almN'] = almN - return_dict['almO'] = almO - return_dict['c'] = c - return_dict['frO'] = frO - return_dict['frN'] = frN - - # frequency-dependent coefficeints in Np/m or dB/m - return_dict['absVibN_dB'] = absVibN - return_dict['absVibO_dB'] = absVibO - return_dict['absClRo_dB'] = absClRo - return_dict['absfull_dB'] = absfull2 - return_dict['absVibN_Np'] = absVibN*np.log(10)/20 - return_dict['absVibO_Np'] = absVibO*np.log(10)/20 - return_dict['absClRo_Np'] = absClRo*np.log(10)/20 - return_dict['absfull_Np'] = absfull2*np.log(10)/20 - - return return_dict + return AirAbsorption( + gamma_p=etaO/c, + gamma=eta/c, + etaO=etaO, + eta=eta, + almN=almN, + almO=almO, + c=c, + frO=frO, + frN=frN, + + absVibN_dB=absVibN, + absVibO_dB=absVibO, + absClRo_dB=absClRo, + absfull_dB=absfull2, + absVibN_Np=absVibN*np.log(10)/20, + absVibO_Np=absVibO*np.log(10)/20, + absClRo_Np=absClRo*np.log(10)/20, + absfull_Np=absfull2*np.log(10)/20, + ) def apply_modal_filter(x, Fs, Tc, rh, pad_t=0.0): @@ -155,8 +185,8 @@ def apply_modal_filter(x, Fs, Tc, rh, pad_t=0.0): wq = wqTs/Ts rd = air_absorption(wq/2/pi, Tc, rh) - alphaq = rd['absfull_Np'] - c = rd['c'] + alphaq = rd.absfull_Np + c = rd.c P0 = np.zeros(xp.shape) P1 = np.zeros(xp.shape) @@ -231,8 +261,8 @@ def apply_ola_filter(x, Fs, Tc, rh, Nw=1024): fv = np.arange(Nfft_h)/Nfft*Fs rd = air_absorption(fv, Tc, rh) - c = rd['c'] - absNp = rd['absfull_Np'] + c = rd.c + absNp = rd.absfull_Np for i in range(xp.shape[0]): pbar = tqdm(total=NF, desc=f'OLA filter channel {i}', ascii=True) @@ -269,7 +299,7 @@ def apply_visco_filter(x, Fs, Tc, rh, NdB=120, t_start=None): function for Stokes' equation", to be presented at the DAFx2021 e-conference. """ rd = air_absorption(1, Tc, rh) - g = rd['gamma_p'] + g = rd.gamma_p Ts = 1/Fs if t_start is None: @@ -313,23 +343,23 @@ def main(): print(f'{Tc=} {rh=}%') rd = air_absorption(f, Tc, rh) - print(f"{rd['almO']=}") - print(f"{rd['almN']=}") - print(f"{rd['c']=}") - print(f"{rd['frO']=}") - print(f"{rd['frN']=}") - print(f"{rd['eta']=}") + print(f"{rd.almO=}") + print(f"{rd.almN=}") + print(f"{rd.c=}") + print(f"{rd.frO=}") + print(f"{rd.frN=}") + print(f"{rd.eta=}") rh = (100 - 10)*np.random.random_sample()+10 Tc = (50 - -20)*np.random.random_sample()-20 print(f'{Tc=} {rh=}%') rd = air_absorption(f, Tc, rh) - print(f"{rd['almO']=}") - print(f"{rd['almN']=}") - print(f"{rd['c']=}") - print(f"{rd['frO']=}") - print(f"{rd['frN']=}") - print(f"{rd['eta']=}") + print(f"{rd.almO=}") + print(f"{rd.almN=}") + print(f"{rd.c=}") + print(f"{rd.frO=}") + print(f"{rd.frN=}") + print(f"{rd.eta=}") if __name__ == '__main__': diff --git a/src/python/pffdtd/analysis/cli.py b/src/python/pffdtd/analysis/cli.py index cd73dff..1fa8899 100644 --- a/src/python/pffdtd/analysis/cli.py +++ b/src/python/pffdtd/analysis/cli.py @@ -9,7 +9,7 @@ from pffdtd.analysis.waterfall import main as waterfall -@click.group(help="Analysis.") +@click.group(help='Analysis.') def analysis(): pass diff --git a/src/python/pffdtd/analysis/response.py b/src/python/pffdtd/analysis/response.py index 43638fa..ade8a8c 100644 --- a/src/python/pffdtd/analysis/response.py +++ b/src/python/pffdtd/analysis/response.py @@ -39,12 +39,12 @@ def fractional_octave_smoothing(magnitudes, fs, nfft, fraction=3): return smoothed -@click.command(name="response", help="Plot frequency response.") +@click.command(name='response', help='Plot frequency response.') @click.argument('filename', nargs=2, type=click.Path(exists=True)) @click.option('--fmin', default=1.0) @click.option('--fmax', default=1000.0) -@click.option('--label_a', default="A") -@click.option('--label_b', default="B") +@click.option('--label_a', default='A') +@click.option('--label_b', default='B') @click.option('--smoothing', default=0.0) @click.option('--target', default=0.0) def main(filename, fmin, fmax, label_a, label_b, smoothing, target): diff --git a/src/python/pffdtd/analysis/room_modes.py b/src/python/pffdtd/analysis/room_modes.py index ff17f20..09a9d4f 100644 --- a/src/python/pffdtd/analysis/room_modes.py +++ b/src/python/pffdtd/analysis/room_modes.py @@ -21,7 +21,7 @@ def find_nearest(array, value): return array[idx] -def collect_wav_paths(folder, pattern="*.wav"): +def collect_wav_paths(folder, pattern='*.wav'): return list(sorted(glob.glob(os.path.join(folder, pattern)))) @@ -31,8 +31,8 @@ def hz_to_note(frequency): # Reference position for A4 in the note list A4_position = 9 # List of note names - note_names = ["C", "C#", "D", "D#", "E", - "F", "F#", "G", "G#", "A", "A#", "B"] + note_names = ['C', 'C#', 'D', 'D#', 'E', + 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] # Calculate the number of semitones between the given frequency and A4 semitones_from_A4 = 12 * np.log2(frequency / A4_frequency) @@ -58,10 +58,10 @@ def room_mode(L, W, H, m, n, p): def room_mode_kind(m, n, p): non_zero = (m != 0) + (n != 0) + (p != 0) if non_zero == 1: - return "axial" + return 'axial' if non_zero == 2: - return "tangential" - return "oblique" + return 'tangential' + return 'oblique' def room_modes(L, W, H, max_order=6): @@ -71,10 +71,10 @@ def room_modes(L, W, H, max_order=6): for p in range(max_order+1): if m+n+p > 0: modes.append({ - "m": m, - "n": n, - "p": p, - "frequency": room_mode(L, W, H, m, n, p) + 'm': m, + 'n': n, + 'p': p, + 'frequency': room_mode(L, W, H, m, n, p) }) return sorted(modes, key=lambda x: x['frequency']) @@ -109,7 +109,7 @@ def detect_room_modes( directory = sim_dir paths = filename if not paths: - paths = collect_wav_paths(directory, "*_out_normalised.wav") + paths = collect_wav_paths(directory, '*_out_normalised.wav') L = length W = width @@ -126,7 +126,7 @@ def detect_room_modes( print(f"{A=:.2f}m^2 {S=:.2f}m^2 {V=:.2f}m^3") print(f"w/h={W/H:.2f} l/h={L/H:.2f} l/w={L/W:.2f}") print(f"FSI({len(modes)}): {frequency_spacing_index(modes):.2f}") - print("") + print('') for mode in modes[:10]: m = mode['m'] @@ -148,7 +148,7 @@ def detect_room_modes( window = windows.hann(buf.shape[0]) buf *= window - nfft = (2**iceil(np.log2(buf.shape[0])))*2 + nfft = (2**iceil(np.log2(buf.shape[0])))*8 spectrum = np.fft.rfft(buf, nfft) freqs = np.fft.rfftfreq(nfft, 1/fs) @@ -157,7 +157,7 @@ def detect_room_modes( dB += 75.0 dB_max = np.max(dB) - peaks, _ = find_peaks(dB, width=2) + peaks, _ = find_peaks(dB, width=2, height=50.0) measured_mode_freqs = freqs[peaks] print(measured_mode_freqs[:10]) @@ -168,7 +168,7 @@ def detect_room_modes( calculated_mode_freqs = [mode['frequency'] for mode in modes][:num_modes] plt.vlines(calculated_mode_freqs, dB_max-80, dB_max+10, - colors='#AAAAAA', linestyles='--', label="Modes") + colors='#AAAAAA', linestyles='--', label='Modes') if len(paths) == 1: plt.plot(measured_mode_freqs, dB[peaks], 'r.', markersize=10, label='Peaks') @@ -179,7 +179,7 @@ def detect_room_modes( error_pct = np.abs(error)/mode*100 print(f"{mode=:03.3f} Hz - {nearest=:03.3f} Hz = {error:03.3f} Hz / {error_pct:.3f} %") - plt.title("") + plt.title('') plt.xlabel('Frequency [Hz]') plt.ylabel('Amplitude [dB]') plt.xscale('log') @@ -195,7 +195,7 @@ def detect_room_modes( return calculated_mode_freqs, measured_mode_freqs -@click.command(name="room-modes", help="Plot room modes.") +@click.command(name='room-modes', help='Plot room modes.') @click.argument('filename', nargs=-1, type=click.Path(exists=True)) @click.option('--sim_dir', type=click.Path(exists=True)) @click.option('--fmin', default=1.0, type=float) diff --git a/src/python/pffdtd/analysis/t60.py b/src/python/pffdtd/analysis/t60.py index b262681..142b445 100644 --- a/src/python/pffdtd/analysis/t60.py +++ b/src/python/pffdtd/analysis/t60.py @@ -16,7 +16,7 @@ from pffdtd.common.plot import plot_styles -def collect_wav_files(directory, pattern="*.wav"): +def collect_wav_files(directory, pattern='*.wav'): search_pattern = os.path.join(directory, pattern) wav_files = glob.glob(search_pattern) return wav_files @@ -88,7 +88,7 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): for f, name in zip(file_times, file_names): ax.semilogx(center_freqs, f, label=f"{name}") else: - ax.semilogx(center_freqs, file_times[0], label="Measurement") + ax.semilogx(center_freqs, file_times[0], label='Measurement') if target: ax.hlines( @@ -97,12 +97,12 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): fmax, color='#555555', label=f"Target {target} s", - linestyles="dashed", + linestyles='dashed', ) - ax.set_title("RT60") - ax.set_ylabel("Decay [s]") - ax.set_xlabel("Frequency [Hz]") + ax.set_title('RT60') + ax.set_ylabel('Decay [s]') + ax.set_xlabel('Frequency [Hz]') ax.xaxis.set_major_formatter(formatter) ax.set_xlim((fmin, fmax)) @@ -130,7 +130,7 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): else: diff = np.insert(np.diff(file_times[0]), 0, 0.0) diff = np.insert(file_times[0][:-1]-file_times[0][1:], 0, 0.0) - ax.semilogx(center_freqs, diff, label="Measurement") + ax.semilogx(center_freqs, diff, label='Measurement') ymin, ymax = np.min(diff), np.max(diff) ax.plot([63.0, 200.0], [0.3, 0.05], color='#555555') @@ -138,14 +138,14 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): [+0.05, -0.05, -0.1, -0.1, +0.3, +0.05, -0.05], [200, 100, k4, k8, fmin, k8, fmin], [k8, k4, k8, k20, 63, k20, 100], - linestyles=["-", "-", "-", "--", "--", "--", "--"], + linestyles=['-', '-', '-', '--', '--', '--', '--'], colors='#555555', - label="EBU Tech 3000" + label='EBU Tech 3000' ) - ax.set_title("Tolerance") - ax.set_ylabel("Difference [s]") - ax.set_xlabel("Frequency [Hz]") + ax.set_title('Tolerance') + ax.set_ylabel('Difference [s]') + ax.set_xlabel('Frequency [Hz]') ax.xaxis.set_major_formatter(formatter) ax.set_xlim((fmin, fmax)) @@ -158,7 +158,7 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): plt.show() -@click.command(name="t60", help="Plot RT60 decay times.") +@click.command(name='t60', help='Plot RT60 decay times.') @click.argument('filename', nargs=-1, type=click.Path(exists=True)) @click.option('--sim_dir', type=click.Path(exists=True)) @click.option('--fmin', default=1.0) @@ -166,11 +166,11 @@ def run(files, fmin, fmax, show_all=False, show_tolerance=True, target=None): @click.option('--target', default=0.0) def main(filename, sim_dir, fmin, fmax, target): if sim_dir and len(filename) > 0: - raise RuntimeError("--sim_dir not valid, when comparing IRs") + raise RuntimeError('--sim_dir not valid, when comparing IRs') files = filename if sim_dir: - files = collect_wav_files(sim_dir, "*_out_normalised.wav") + files = collect_wav_files(sim_dir, '*_out_normalised.wav') run( list(sorted(files)), diff --git a/src/python/pffdtd/analysis/waterfall.py b/src/python/pffdtd/analysis/waterfall.py index 7b56aff..87e4886 100644 --- a/src/python/pffdtd/analysis/waterfall.py +++ b/src/python/pffdtd/analysis/waterfall.py @@ -9,7 +9,7 @@ from scipy.io import wavfile -@click.command(name="waterfall", help="Plot waterfall decay plot.") +@click.command(name='waterfall', help='Plot waterfall decay plot.') @click.argument('filename', nargs=1, type=click.Path(exists=True)) def main(filename): fs, rir = wavfile.read(filename) @@ -43,12 +43,12 @@ def main(filename): fig.update_layout( title='Decay Times', scene={ - "xaxis_title": 'X: Time [s]', - "yaxis_title": 'Y: Frequency [Hz]', - "zaxis_title": 'Z: Amplitude [dB]', + 'xaxis_title': 'X: Time [s]', + 'yaxis_title': 'Y: Frequency [Hz]', + 'zaxis_title': 'Z: Amplitude [dB]', - "xaxis": {"range": [0, 1]}, - "yaxis": {"type": "log"}, + 'xaxis': {'range': [0, 1]}, + 'yaxis': {'type': 'log'}, # "zaxis": {"range": [-60, 0]}, } ) diff --git a/src/python/pffdtd/common/misc.py b/src/python/pffdtd/common/misc.py index 77e0a90..100b8d9 100644 --- a/src/python/pffdtd/common/misc.py +++ b/src/python/pffdtd/common/misc.py @@ -27,7 +27,7 @@ def clear_dat_folder(dat_folder_str=None): def yes_or_no(question): - while "the answer is invalid": + while 'the answer is invalid': reply = str(input(question+' (y/n): ')).lower().strip() if reply[:1] == 'y': return True diff --git a/src/python/pffdtd/common/timerdict.py b/src/python/pffdtd/common/timerdict.py index 95d8d85..45d19e4 100644 --- a/src/python/pffdtd/common/timerdict.py +++ b/src/python/pffdtd/common/timerdict.py @@ -5,27 +5,30 @@ """ import time -#timer class using dict for sub-timers + class TimerDict: + """timer class using dict for sub-timers + """ + def __init__(self): - self.d = {} #actual dict - self.t = {} #to check toc'ed + self.d = {} # actual dict + self.t = {} # to check toc'ed def __del__(self): for key in self.t.keys(): if not self.t[key]: print(f'TimerDict: "{key}" never toc\'ed') - #tic (start) - def tic(self,key=0): + # tic (start) + def tic(self, key=0): self.d[key] = time.time() self.t[key] = False - def inc(self,key=0,delta=0): + def inc(self, key=0, delta=0): self.d[key] -= delta - #toc and print - def toc(self,key=0,print_elapsed=True): + # toc and print + def toc(self, key=0, print_elapsed=True): assert key in self.d.keys() delta = time.time()-self.d[key] self.t[key] = True @@ -33,15 +36,15 @@ def toc(self,key=0,print_elapsed=True): print(f'*TIMER {key}: elapsed = {delta:.4f} s') return delta - #toc and pass back f-string - def ftoc(self,key=0): + # toc and pass back f-string + def ftoc(self, key=0): assert key in self.d.keys() delta = time.time()-self.d[key] self.t[key] = True return f'** TIMED {key}: elapsed = {delta:.4f} s' - #toc quietly - def tocq(self,key=0): + # toc quietly + def tocq(self, key=0): assert key in self.d.keys() self.t[key] = True return time.time()-self.d[key] diff --git a/src/python/pffdtd/diffusor/prd.py b/src/python/pffdtd/diffusor/prd.py index c467c38..6bd7fb0 100644 --- a/src/python/pffdtd/diffusor/prd.py +++ b/src/python/pffdtd/diffusor/prd.py @@ -36,5 +36,5 @@ def main(): print(primitive_root_diffuser(17)) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/src/python/pffdtd/diffusor/qrd.py b/src/python/pffdtd/diffusor/qrd.py index 6a91eaa..070adff 100644 --- a/src/python/pffdtd/diffusor/qrd.py +++ b/src/python/pffdtd/diffusor/qrd.py @@ -31,17 +31,17 @@ def main(): print(f"width = {well_width*100:.2f} cm") print(f"depth = {design_depth*100:.2f} cm") print(f"seat = {seat_distance*100:.2f} cm") - print("") + print('') print(f"scatter = {fmin:.2f} Hz") print(f"diffuse = {design_frequency:.2f} Hz") print(f"HF cutoff = {fmax:.2f} Hz") print(f"plate = {plate_frequency:.2f} Hz") - print("") + print('') - print(f"wells = {np.round(w*100,2)} cm") + print(f"wells = {np.round(w*100, 2)} cm") print(f"max depth = {np.max(w)*100:.2f} cm") -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/src/python/pffdtd/geometry/box.py b/src/python/pffdtd/geometry/box.py index 90e762d..ba396bc 100644 --- a/src/python/pffdtd/geometry/box.py +++ b/src/python/pffdtd/geometry/box.py @@ -35,12 +35,12 @@ def __init__(self, Lx=None, Ly=None, Lz=None, Rax=None, Rang=None, shift=None, c def init(self, Lx, Ly, Lz, Rax, Rang, shift): verts = np.array([[0., 0., 0.], [0., 0., Lz], [0., Ly, 0.], [0., Ly, Lz], [ - Lx, 0., 0.], [Lx, 0., Lz], [Lx, Ly, 0.], [Lx, Ly, Lz]]) + Lx, 0., 0.], [Lx, 0., Lz], [Lx, Ly, 0.], [Lx, Ly, Lz]]) if self.centered: verts -= 0.5*np.array([Lx, Ly, Lz]) A = np.array([[-1., 0., 0.], [0., -1., 0.], [0., 0., -1.], - [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) + [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) if self.centered: b = 0.5*np.array([Lx, Ly, Lz, Lx, Ly, Lz]) @@ -67,11 +67,11 @@ def init(self, Lx, Ly, Lz, Rax, Rang, shift): self.bmax = bmax self.edges = np.array([[0, 1], [0, 2], [0, 4], [1, 3], [1, 5], [2, 3], [ - 2, 6], [4, 5], [4, 6], [3, 7], [5, 7], [6, 7]]) + 2, 6], [4, 5], [4, 6], [3, 7], [5, 7], [6, 7]]) self.tris = np.array([[0, 1, 3], [0, 3, 2], [1, 7, 3], [1, 5, 7], [0, 2, 6], [ - 0, 6, 4], [4, 7, 5], [4, 6, 7], [2, 3, 7], [2, 7, 6], [0, 5, 1], [0, 4, 5]]) + 0, 6, 4], [4, 7, 5], [4, 6, 7], [2, 3, 7], [2, 7, 6], [0, 5, 1], [0, 4, 5]]) self.quads = np.array([[0, 1, 3, 2], [0, 4, 5, 1], [4, 6, 7, 5], [ - 1, 5, 7, 3], [2, 3, 7, 6], [0, 2, 6, 4]]) + 1, 5, 7, 3], [2, 3, 7, 6], [0, 2, 6, 4]]) def randomise(self): Lx = 10*npr.random() diff --git a/src/python/pffdtd/geometry/math.py b/src/python/pffdtd/geometry/math.py index 5b50ef7..37aafac 100644 --- a/src/python/pffdtd/geometry/math.py +++ b/src/python/pffdtd/geometry/math.py @@ -106,8 +106,8 @@ def iround(x): return np.int_(np.round(x)) -def to_ixy(x, y, Nx, Ny, order="row"): - if order == "row": +def to_ixy(x, y, Nx, Ny, order='row'): + if order == 'row': return x*Ny+y return y*Nx+x diff --git a/src/python/pffdtd/geometry/tri_ray_intersection.py b/src/python/pffdtd/geometry/tri_ray_intersection.py index c7d9402..5ce8c19 100644 --- a/src/python/pffdtd/geometry/tri_ray_intersection.py +++ b/src/python/pffdtd/geometry/tri_ray_intersection.py @@ -15,14 +15,16 @@ import numpy as np from numpy import array as npa -from pffdtd.geometry.math import dotv,normalise,vecnorm +from pffdtd.geometry.math import dotv, normalise, vecnorm from pffdtd.common.asserts import assert_np_array_float from pffdtd.geometry.tris_precompute import tris_precompute -#d_eps is a distance eps, cp is for coplanarity (non-dimensional) -#pretty standard test: check if coplanar, then check point-on-plane inside edge functions -def tri_ray_intersection(ray_o,ray_d,tri_pre,d_eps=1e-6,cp_eps=1e-6): - #returns hit independent of triangle orientation wrt ray +# d_eps is a distance eps, cp is for coplanarity (non-dimensional) +# pretty standard test: check if coplanar, then check point-on-plane inside edge functions + + +def tri_ray_intersection(ray_o, ray_d, tri_pre, d_eps=1e-6, cp_eps=1e-6): + # returns hit independent of triangle orientation wrt ray assert_np_array_float(ray_o) assert_np_array_float(ray_d) @@ -31,10 +33,10 @@ def tri_ray_intersection(ray_o,ray_d,tri_pre,d_eps=1e-6,cp_eps=1e-6): assert ray_d.ndim == 1 assert ray_o.size == 3 assert ray_d.size == 3 - d_eps=np.abs(d_eps) - cp_eps=np.abs(cp_eps) + d_eps = np.abs(d_eps) + cp_eps = np.abs(cp_eps) - ray_un = normalise(ray_d) #normalise in case + ray_un = normalise(ray_d) # normalise in case tri_unor = tri_pre['unor'] tri_cent = tri_pre['cent'] @@ -44,32 +46,34 @@ def tri_ray_intersection(ray_o,ray_d,tri_pre,d_eps=1e-6,cp_eps=1e-6): tinf = np.inf - #check if coplanar - beta = np.dot(ray_un,tri_unor) - #print(f'{beta=}') - if np.abs(beta)d_eps: - return False,tinf - if np.dot(pop-0.5*(tri_b+tri_c),tri_pre['ebc_unor'])>d_eps: - return False,tinf - if np.dot(pop-0.5*(tri_c+tri_a),tri_pre['eca_unor'])>d_eps: - return False,tinf + # check inside edge vectors + if np.dot(pop-0.5*(tri_a+tri_b), tri_pre['eab_unor']) > d_eps: + return False, tinf + if np.dot(pop-0.5*(tri_b+tri_c), tri_pre['ebc_unor']) > d_eps: + return False, tinf + if np.dot(pop-0.5*(tri_c+tri_a), tri_pre['eca_unor']) > d_eps: + return False, tinf + + return True, t - return True,t +# one ray / many tris, but also works with one tri / many rays -#one ray / many tris, but also works with one tri / many rays -def tri_ray_intersection_vec(ray_o,ray_d,tris_pre,d_eps=1e-6,cp_eps=1e-6): + +def tri_ray_intersection_vec(ray_o, ray_d, tris_pre, d_eps=1e-6, cp_eps=1e-6): assert_np_array_float(ray_o) assert_np_array_float(ray_d) @@ -79,45 +83,46 @@ def tri_ray_intersection_vec(ray_o,ray_d,tris_pre,d_eps=1e-6,cp_eps=1e-6): d_eps = np.abs(d_eps) cp_eps = np.abs(cp_eps) - ray_un = normalise(ray_d) #normalise in case + ray_un = normalise(ray_d) # normalise in case tris_unor = tris_pre['unor'] tris_cent = tris_pre['cent'] - tris_c = tris_pre['v'][:,2,:] - tris_b = tris_pre['v'][:,1,:] - tris_a = tris_pre['v'][:,0,:] + tris_c = tris_pre['v'][:, 2, :] + tris_b = tris_pre['v'][:, 1, :] + tris_a = tris_pre['v'][:, 0, :] + + # check if coplanar + beta = dotv(ray_un, tris_unor) - #check if coplanar - beta = dotv(ray_un,tris_unor) + fail = np.abs(beta) < cp_eps + beta[fail] = -np.finfo(np.float64).eps # fake value to avoid divide by zero - fail = np.abs(beta) d_eps + fail |= dotv(pop-0.5*(tris_b+tris_c), tris_pre['ebc_unor']) > d_eps + fail |= dotv(pop-0.5*(tris_c+tris_a), tris_pre['eca_unor']) > d_eps - #check inside edge vectors with distance epsilon - fail |= dotv(pop-0.5*(tris_a+tris_b),tris_pre['eab_unor'])>d_eps - fail |= dotv(pop-0.5*(tris_b+tris_c),tris_pre['ebc_unor'])>d_eps - fail |= dotv(pop-0.5*(tris_c+tris_a),tris_pre['eca_unor'])>d_eps + t_ret[~fail] = t[~fail] - t_ret[~fail]=t[~fail] + return ~fail, t_ret - return ~fail,t_ret def main(): - #some randomized tests + # some randomized tests import numpy.random as npr import argparse parser = argparse.ArgumentParser() - parser.add_argument('--nodraw', action='store_true',help='draw') - parser.add_argument('--trials', type=int,help='Nvox roughly') + parser.add_argument('--nodraw', action='store_true', help='draw') + parser.add_argument('--trials', type=int, help='Nvox roughly') parser.set_defaults(nodraw=False) parser.set_defaults(trials=1) args = parser.parse_args() @@ -126,121 +131,121 @@ def main(): if draw: from mayavi import mlab - from tvtk.api import tvtk #only for z-up - assert args.trials<4 + from tvtk.api import tvtk # only for z-up + assert args.trials < 4 for _ in range(args.trials): #################### - #one ray many tris + # one ray many tris #################### Ntris = 5 - #generate tris - vv = npr.randn(Ntris,3,3) - pts = vv.reshape((-1,3)) - tris = np.arange(Ntris*3).reshape(-1,3) + # generate tris + vv = npr.randn(Ntris, 3, 3) + pts = vv.reshape((-1, 3)) + tris = np.arange(Ntris*3).reshape(-1, 3) - bmin = np.amin(pts,axis=0) - bmax = np.amax(pts,axis=0) + bmin = np.amin(pts, axis=0) + bmax = np.amax(pts, axis=0) scale = vecnorm(bmax-bmin) - tris_pre = tris_precompute(pts=pts,tris=tris) + tris_pre = tris_precompute(pts=pts, tris=tris) - #ray direction and origin (make coming from outside and pointing in) + # ray direction and origin (make coming from outside and pointing in) ro = normalise(npr.randn(3))*scale - rd = normalise(np.mean(pts,axis=0)-ro) + rd = normalise(np.mean(pts, axis=0)-ro) - swap = npr.randint(0,2) - if swap==1: #origin inside triangle cluster and pointing outwards - ro,rd = rd,normalise(ro) + swap = npr.randint(0, 2) + if swap == 1: # origin inside triangle cluster and pointing outwards + ro, rd = rd, normalise(ro) - hit = np.full(Ntris,False) - dist = np.full(Ntris,np.nan) - for ti in range(0,Ntris): - hit[ti],dist[ti] = tri_ray_intersection(ro,rd,tris_pre[ti]) + hit = np.full(Ntris, False) + dist = np.full(Ntris, np.nan) + for ti in range(0, Ntris): + hit[ti], dist[ti] = tri_ray_intersection(ro, rd, tris_pre[ti]) - hit2,dist2 = tri_ray_intersection_vec(ro,rd,tris_pre) + hit2, dist2 = tri_ray_intersection_vec(ro, rd, tris_pre) print(f'{dist=}') - assert np.all(hit==hit2),'mismatch!' - assert np.all(dist==dist2),'mismatch!' #maybe need np.allclose + assert np.all(hit == hit2), 'mismatch!' + assert np.all(dist == dist2), 'mismatch!' # maybe need np.allclose - #print(scale) + # print(scale) if draw: fig = mlab.figure() - mlab.quiver3d(*ro, *rd,color=(0,1,0)) + mlab.quiver3d(*ro, *rd, color=(0, 1, 0)) if swap: - mlab.plot3d(*np.c_[ro,ro+rd*0.5*scale],color=(0,1,0)) + mlab.plot3d(*np.c_[ro, ro+rd*0.5*scale], color=(0, 1, 0)) else: - mlab.plot3d(*np.c_[ro,ro+rd*2*scale],color=(0,1,0)) - mlab.points3d(*ro,mode='cube',scale_factor=scale/100.0,scale_mode='vector') - for ti in range(0,Ntris): + mlab.plot3d(*np.c_[ro, ro+rd*2*scale], color=(0, 1, 0)) + mlab.points3d(*ro, mode='cube', scale_factor=scale/100.0, scale_mode='vector') + for ti in range(0, Ntris): trip = tris_pre[ti] - mlab.plot3d(*np.c_[trip['cent'],trip['cent']+trip['unor']*np.sqrt(trip['area'])],color=(0,0,0)) + mlab.plot3d(*np.c_[trip['cent'], trip['cent']+trip['unor']*np.sqrt(trip['area'])], color=(0, 0, 0)) if hit[ti]: - color = (1,0,0) - mlab.points3d(*(ro+rd*dist[ti]),mode='sphere',scale_factor=scale/50.0,scale_mode='vector',color=(0,0,1)) + color = (1, 0, 0) + mlab.points3d(*(ro+rd*dist[ti]), mode='sphere', scale_factor=scale/50.0, scale_mode='vector', color=(0, 0, 1)) else: - color = (1,1,1) - mlab.triangular_mesh(*(pts.T),tris[ti][None,:],opacity=1,color=color) + color = (1, 1, 1) + mlab.triangular_mesh(*(pts.T), tris[ti][None, :], opacity=1, color=color) mlab.orientation_axes() fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain() #################### - #one tri many rays + # one tri many rays #################### Nrays = 5 - #generate tris - pts = npr.randn(3,3) + # generate tris + pts = npr.randn(3, 3) tri = np.arange(3) - bmin = np.amin(pts,axis=0) - bmax = np.amax(pts,axis=0) + bmin = np.amin(pts, axis=0) + bmax = np.amax(pts, axis=0) scale = vecnorm(bmax-bmin) + tri_pre = tris_precompute(pts=pts, tris=npa([tri])) - tri_pre = tris_precompute(pts=pts,tris=npa([tri])) + ro = normalise(npr.randn(Nrays, 3))*scale + rd = normalise(npr.random((Nrays, 3))*(bmax-bmin)+bmin - ro) - ro = normalise(npr.randn(Nrays,3))*scale - rd = normalise(npr.random((Nrays,3))*(bmax-bmin)+bmin - ro) + swap = npr.randint(0, 2) + if swap == 1: + ro, rd = rd, normalise(ro) - swap = npr.randint(0,2) - if swap==1: - ro,rd = rd,normalise(ro) - - hit = np.full(Nrays,False) - dist = np.full(Nrays,np.nan) - for ri in range(0,Nrays): - hit[ri],dist[ri] = tri_ray_intersection(ro[ri],rd[ri],tri_pre[0]) + hit = np.full(Nrays, False) + dist = np.full(Nrays, np.nan) + for ri in range(0, Nrays): + hit[ri], dist[ri] = tri_ray_intersection(ro[ri], rd[ri], tri_pre[0]) print(f'{dist=}') - hit2,dist2 = tri_ray_intersection_vec(ro,rd,tri_pre) + hit2, dist2 = tri_ray_intersection_vec(ro, rd, tri_pre) - assert np.all(hit==hit2),'mismatch!' - assert np.all(dist==dist2),'mismatch!' #maybe need np.allclose + assert np.all(hit == hit2), 'mismatch!' + assert np.all(dist == dist2), 'mismatch!' # maybe need np.allclose if draw: fig = mlab.figure() - mlab.triangular_mesh(*(pts.T),tri[None,:],opacity=1,color=(1,1,1)) - #mlab.points3d(*(pts.T),mode='sphere',resolution=3,opacity=1,color=(1,1,1),scale_factor=scale/50.0) - for ri in range(0,Nrays): + mlab.triangular_mesh(*(pts.T), tri[None, :], opacity=1, color=(1, 1, 1)) + # mlab.points3d(*(pts.T),mode='sphere',resolution=3,opacity=1,color=(1,1,1),scale_factor=scale/50.0) + for ri in range(0, Nrays): if hit[ri]: - color = (1,0,0) - mlab.points3d(*(ro[ri]+rd[ri]*dist[ri]),mode='sphere',scale_factor=scale/25.0,scale_mode='vector',color=(0,0,1)) + color = (1, 0, 0) + mlab.points3d(*(ro[ri]+rd[ri]*dist[ri]), mode='sphere', scale_factor=scale/25.0, scale_mode='vector', color=(0, 0, 1)) else: - color = (1,1,1) - mlab.points3d(*ro[ri],mode='cube',scale_factor=scale/20.0,scale_mode='vector') - mlab.quiver3d(*ro[ri], *rd[ri],color=color) + color = (1, 1, 1) + mlab.points3d(*ro[ri], mode='cube', scale_factor=scale/20.0, scale_mode='vector') + mlab.quiver3d(*ro[ri], *rd[ri], color=color) if swap: - mlab.plot3d(*np.c_[ro[ri],ro[ri]+rd[ri]*scale],color=color) + mlab.plot3d(*np.c_[ro[ri], ro[ri]+rd[ri]*scale], color=color) else: - mlab.plot3d(*np.c_[ro[ri],ro[ri]+rd[ri]*4*scale],color=color) + mlab.plot3d(*np.c_[ro[ri], ro[ri]+rd[ri]*4*scale], color=color) mlab.orientation_axes() fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain() if draw: mlab.show() + if __name__ == '__main__': main() diff --git a/src/python/pffdtd/geometry/tris_precompute.py b/src/python/pffdtd/geometry/tris_precompute.py index 2130447..754e290 100644 --- a/src/python/pffdtd/geometry/tris_precompute.py +++ b/src/python/pffdtd/geometry/tris_precompute.py @@ -64,23 +64,23 @@ def tris_precompute(pts=None, tris=None): custom_dtype = [\ # ('a',np.float64,(3,)),\ # ('b',np.float64,(3,)),\ - # ('c',np.float64,(3,)),\ - ('v', np.float64, (3, 3)), \ - ('ab', np.float64, (3,)), \ - ('bc', np.float64, (3,)), \ - ('ca', np.float64, (3,)), \ - ('nor', np.float64, (3,)), \ - ('unor', np.float64, (3,)), \ - ('eab_unor', np.float64, (3,)), \ - ('ebc_unor', np.float64, (3,)), \ - ('eca_unor', np.float64, (3,)), \ - ('cent', np.float64, (3,)), \ - ('bmin', np.float64, (3,)), \ - ('bmax', np.float64, (3,)), \ - ('l2ab', np.float64), \ - ('l2bc', np.float64), \ - ('l2ca', np.float64), \ - ('area', np.float64), \ + # ('c',np.float64,(3,)), + ('v', np.float64, (3, 3)), + ('ab', np.float64, (3,)), + ('bc', np.float64, (3,)), + ('ca', np.float64, (3,)), + ('nor', np.float64, (3,)), + ('unor', np.float64, (3,)), + ('eab_unor', np.float64, (3,)), + ('ebc_unor', np.float64, (3,)), + ('eca_unor', np.float64, (3,)), + ('cent', np.float64, (3,)), + ('bmin', np.float64, (3,)), + ('bmax', np.float64, (3,)), + ('l2ab', np.float64), + ('l2bc', np.float64), + ('l2ca', np.float64), + ('area', np.float64), ] tris_pre = np.zeros(Ntris, dtype=custom_dtype) diff --git a/src/python/pffdtd/materials/build.py b/src/python/pffdtd/materials/build.py index f3500aa..8f9c58e 100644 --- a/src/python/pffdtd/materials/build.py +++ b/src/python/pffdtd/materials/build.py @@ -12,12 +12,13 @@ ) -@click.command(help="Build materials.") +@click.command(help='Build materials.') @click.option('--plot/--no-plot', default=False) @click.argument('write_folder', nargs=1, type=click.Path(exists=True)) def build(write_folder, plot): write_folder = Path(write_folder) + # autopep8: off absorber_8000_50mm = np.array([0.01, 0.02, 0.03, 0.05, 0.26, 0.59, 0.88, 0.94, 0.95, 0.93, 0.90]) absorber_8000_100mm = np.array([0.02, 0.03, 0.05, 0.30, 0.69, 0.92, 0.93, 0.94, 0.95, 0.93, 0.90]) absorber_8000_200mm = np.array([0.05, 0.10, 0.40, 0.85, 0.89, 0.92, 0.93, 0.94, 0.95, 0.93, 0.90]) @@ -103,3 +104,5 @@ def build(write_folder, plot): # #input DEF values directly # write_freq_dep_mat(npa([[0,1.0,0],[2,3,4]]),filename=Path(write_folder / 'ex_mat.h5')) + + # autopep8: on diff --git a/src/python/pffdtd/materials/cli.py b/src/python/pffdtd/materials/cli.py index 2e60119..915ba71 100644 --- a/src/python/pffdtd/materials/cli.py +++ b/src/python/pffdtd/materials/cli.py @@ -9,12 +9,12 @@ from pffdtd.materials.build import build -@click.group(help="Materials.") +@click.group(help='Materials.') def materials(): pass -@materials.command(help="Plot material.") +@materials.command(help='Plot material.') @click.argument('material_file', nargs=1, type=click.Path(exists=True)) def plot(material_file): frequencies = np.logspace(np.log10(10), np.log10(20e3), 4000) diff --git a/src/python/pffdtd/sim2d/cli.py b/src/python/pffdtd/sim2d/cli.py index a937f23..8467e81 100644 --- a/src/python/pffdtd/sim2d/cli.py +++ b/src/python/pffdtd/sim2d/cli.py @@ -7,7 +7,7 @@ from pffdtd.sim2d import process_outputs -@click.group(help="2D wave-equation.") +@click.group(help='2D wave-equation.') def sim2d(): pass diff --git a/src/python/pffdtd/sim2d/engine.py b/src/python/pffdtd/sim2d/engine.py index 8fd863f..d15e89e 100644 --- a/src/python/pffdtd/sim2d/engine.py +++ b/src/python/pffdtd/sim2d/engine.py @@ -11,7 +11,7 @@ class Engine2D: - def __init__(self, sim_dir, out="out.h5", video=False): + def __init__(self, sim_dir, out='out.h5', video=False): self.sim_dir = Path(sim_dir) self.video = video self.output_file = out diff --git a/src/python/pffdtd/sim2d/process_outputs.py b/src/python/pffdtd/sim2d/process_outputs.py index 76c4c14..3faf837 100644 --- a/src/python/pffdtd/sim2d/process_outputs.py +++ b/src/python/pffdtd/sim2d/process_outputs.py @@ -12,7 +12,7 @@ from pffdtd.common.wavfile import save_as_wav_files -@click.command(name="process-outputs", help="Process raw simulation output.") +@click.command(name='process-outputs', help='Process raw simulation output.') @click.option('--diff', is_flag=True) @click.option('--fmin', default=20.0) @click.option('--sim_dir', type=click.Path(exists=True)) @@ -20,7 +20,7 @@ def main(fmin, diff, sim_dir, out_file): sim_dir = pathlib.Path(sim_dir) - constants = h5py.File(sim_dir / "constants.h5", 'r') + constants = h5py.File(sim_dir / 'constants.h5', 'r') fs = float(constants['fs'][...]) fmax = float(constants['fmax'][...]) Ts = 1/fs diff --git a/src/python/pffdtd/sim2d/report.py b/src/python/pffdtd/sim2d/report.py index c76926b..5ec7b13 100644 --- a/src/python/pffdtd/sim2d/report.py +++ b/src/python/pffdtd/sim2d/report.py @@ -23,7 +23,7 @@ def main(): out_file = args.out_file[0] sim_dir = Path(args.sim_dir) - constants = h5py.File(sim_dir / "constants.h5", 'r') + constants = h5py.File(sim_dir / 'constants.h5', 'r') fs = float(constants['fs'][...]) fmax = float(constants['fmax'][...]) Ts = 1/fs @@ -74,13 +74,13 @@ def main(): print(times.shape) modes = room_modes(3, 3, 3)[:30] - modes_f = [mode["frequency"] for mode in modes] + modes_f = [mode['frequency'] for mode in modes] plt.plot(times, out.squeeze(), label=f'{15}deg') # plt.plot(times, out[15, :], label=f'{15}deg') # plt.plot(times, out[45, :], label=f'{45}deg') # plt.plot(times, out[90, :], label=f'{90}deg') - plt.grid(which="both") + plt.grid(which='both') plt.legend() plt.show() @@ -88,10 +88,10 @@ def main(): # plt.semilogx(frequencies, dB[15, :], label=f'{15}deg') # plt.semilogx(frequencies, dB[45, :], label=f'{45}deg') # plt.semilogx(frequencies, dB[90, :], label=f'{90}deg') - plt.vlines(modes_f, -60, 0.0, colors="r", linestyles="--") + plt.vlines(modes_f, -60, 0.0, colors='r', linestyles='--') plt.xlim((10, 500)) plt.ylim((-80, 0)) - plt.grid(which="both") + plt.grid(which='both') plt.legend() plt.show() diff --git a/src/python/pffdtd/sim2d/run.py b/src/python/pffdtd/sim2d/run.py index a8a8d63..7d2bbe0 100644 --- a/src/python/pffdtd/sim2d/run.py +++ b/src/python/pffdtd/sim2d/run.py @@ -6,9 +6,9 @@ from pffdtd.sim2d.engine import Engine2D -@click.command(name="run", help="Run simulation.") +@click.command(name='run', help='Run simulation.') @click.option('--sim_dir', type=click.Path(exists=True)) -@click.option('--out', default="out.h5") +@click.option('--out', default='out.h5') @click.option('--video', is_flag=True) def main(sim_dir, out, video): engine = Engine2D(sim_dir=sim_dir, out=out, video=video) diff --git a/src/python/pffdtd/sim3d/cli.py b/src/python/pffdtd/sim3d/cli.py index 8c48691..86d882a 100644 --- a/src/python/pffdtd/sim3d/cli.py +++ b/src/python/pffdtd/sim3d/cli.py @@ -5,12 +5,14 @@ from pffdtd.sim3d import process_outputs from pffdtd.sim3d import room_geometry +from pffdtd.sim3d import setup -@click.group(help="3D wave-equation.") +@click.group(help='3D wave-equation.') def sim3d(): pass sim3d.add_command(process_outputs.main) sim3d.add_command(room_geometry.main) +sim3d.add_command(setup.main) diff --git a/src/python/pffdtd/sim3d/constants.py b/src/python/pffdtd/sim3d/constants.py index e9e8ed3..0eaed83 100644 --- a/src/python/pffdtd/sim3d/constants.py +++ b/src/python/pffdtd/sim3d/constants.py @@ -46,7 +46,7 @@ def __init__(self, Tc, rh, h=None, fs=None, fmax=None, PPW=None, fcc=False, verb Ts = h/c*l fs = 1/Ts else: - raise Exception("Invalid combination of h, fs, fmax & PPW") + raise Exception('Invalid combination of h, fs, fmax & PPW') self.h = h self.c = c diff --git a/src/python/pffdtd/sim3d/engine.py b/src/python/pffdtd/sim3d/engine.py index 97d6475..8ead699 100644 --- a/src/python/pffdtd/sim3d/engine.py +++ b/src/python/pffdtd/sim3d/engine.py @@ -25,14 +25,15 @@ from pffdtd.common.timerdict import TimerDict from pffdtd.common.misc import get_default_nprocs -from pffdtd.geometry.math import ind2sub3d,rel_diff +from pffdtd.geometry.math import ind2sub3d, rel_diff + +MMb = 12 # max allowed number of branches -MMb = 12 #max allowed number of branches class EnginePython3D: - def __init__(self,sim_dir,energy_on=False,nthreads=None): + def __init__(self, sim_dir, energy_on=False, nthreads=None): self.sim_dir = Path(sim_dir) - self.energy_on = energy_on #will calculate energy + self.energy_on = energy_on # will calculate energy if nthreads is None: nthreads = get_default_nprocs() self.print(f'numba set for {nthreads=}') @@ -44,59 +45,59 @@ def __init__(self,sim_dir,energy_on=False,nthreads=None): self.set_coeffs() self.checks() - def print(self,fstring): + def print(self, fstring): print(f'--ENGINE: {fstring}') def load_h5_data(self): self.print('loading data..') sim_dir = self.sim_dir - #here: - #bn: bn (full) - #bnr: bn-rigid - #bnl: bn-lossy (fd updates) - #bnl ∩ bnr = ø , bnl ∪ bnr = bn - - h5f = h5py.File(sim_dir / Path('vox_out.h5'),'r') - self.adj_bn = h5f['adj_bn'][...] #full - self.bn_ixyz = h5f['bn_ixyz'][...] #full - self.Nx = h5f['Nx'][()] - self.Ny = h5f['Ny'][()] - self.Nz = h5f['Nz'][()] - self.xv = h5f['xv'][()] #for plotting - self.yv = h5f['yv'][()] #for plotting - self.zv = h5f['zv'][()] #for plotting - mat_bn = h5f['mat_bn'][...] - saf_bn = h5f['saf_bn'][...] + # here: + # bn: bn (full) + # bnr: bn-rigid + # bnl: bn-lossy (fd updates) + # bnl ∩ bnr = ø , bnl ∪ bnr = bn + + h5f = h5py.File(sim_dir / Path('vox_out.h5'), 'r') + self.adj_bn = h5f['adj_bn'][...] # full + self.bn_ixyz = h5f['bn_ixyz'][...] # full + self.Nx = h5f['Nx'][()] + self.Ny = h5f['Ny'][()] + self.Nz = h5f['Nz'][()] + self.xv = h5f['xv'][()] # for plotting + self.yv = h5f['yv'][()] # for plotting + self.zv = h5f['zv'][()] # for plotting + mat_bn = h5f['mat_bn'][...] + saf_bn = h5f['saf_bn'][...] h5f.close() - ii = mat_bn>-1 + ii = mat_bn > -1 self.saf_bnl = saf_bn[ii] self.mat_bnl = mat_bn[ii] self.bnl_ixyz = self.bn_ixyz[ii] - h5f = h5py.File(sim_dir / Path('signals.h5'),'r') - self.in_ixyz = h5f['in_ixyz'][...] - self.out_ixyz = h5f['out_ixyz'][...] - self.out_alpha = h5f['out_alpha'][...] - self.out_reorder = h5f['out_reorder'][...] - self.in_sigs = h5f['in_sigs'][...] - self.Ns = h5f['Ns'][()] - self.Nr = h5f['Nr'][()] - self.Nt = h5f['Nt'][()] + h5f = h5py.File(sim_dir / Path('signals.h5'), 'r') + self.in_ixyz = h5f['in_ixyz'][...] + self.out_ixyz = h5f['out_ixyz'][...] + self.out_alpha = h5f['out_alpha'][...] + self.out_reorder = h5f['out_reorder'][...] + self.in_sigs = h5f['in_sigs'][...] + self.Ns = h5f['Ns'][()] + self.Nr = h5f['Nr'][()] + self.Nt = h5f['Nt'][()] h5f.close() - h5f = h5py.File(sim_dir / Path('constants.h5'),'r') - self.c = h5f['c'][()] - self.h = h5f['h'][()] - self.Ts = h5f['Ts'][()] - self.l = h5f['l'][()] - self.l2 = h5f['l2'][()] + h5f = h5py.File(sim_dir / Path('constants.h5'), 'r') + self.c = h5f['c'][()] + self.h = h5f['h'][()] + self.Ts = h5f['Ts'][()] + self.l = h5f['l'][()] + self.l2 = h5f['l2'][()] self.fcc_flag = h5f['fcc_flag'][()] h5f.close() - self.fcc = self.fcc_flag>0 + self.fcc = self.fcc_flag > 0 if self.fcc: - assert self.fcc_flag==1 + assert self.fcc_flag == 1 self.print(f'Nx={self.Nx} Ny={self.Ny} Nz={self.Nz}') self.print(f'h={self.h} Ts={self.Ts} c={self.c}, fs={1/self.Ts}') @@ -104,32 +105,31 @@ def load_h5_data(self): self.print(f'Nr={self.Nr} Ns={self.Ns} Nt={self.Nt}') if self.fcc: - assert self.Nx%2 ==0 - assert self.Ny%2 ==0 - assert self.Nz%2 ==0 + assert self.Nx % 2 == 0 + assert self.Ny % 2 == 0 + assert self.Nz % 2 == 0 self.print('On FCC subgrid') assert self.adj_bn.shape[1] == 12 if self.fcc: - self.ssaf_bnl = self.saf_bnl*0.5/np.sqrt(2.0) #rescaled by S*h/V - assert self.l<=1.0 - assert self.l2<=1.0 + self.ssaf_bnl = self.saf_bnl*0.5/np.sqrt(2.0) # rescaled by S*h/V + assert self.l <= 1.0 + assert self.l2 <= 1.0 else: self.ssaf_bnl = self.saf_bnl - assert self.l<=np.sqrt(1/3) - assert self.l2<=1/3 - + assert self.l <= np.sqrt(1/3) + assert self.l2 <= 1/3 - h5f = h5py.File(Path(sim_dir / Path('materials.h5')),'r') + h5f = h5py.File(Path(sim_dir / Path('materials.h5')), 'r') Nmat = h5f['Nmat'][()] - DEF = np.zeros((Nmat,MMb,3)) + DEF = np.zeros((Nmat, MMb, 3)) Mb = h5f['Mb'][...] for i in range(Nmat): dataset = h5f[f'mat_{i:02d}_DEF'][...] assert Mb[i] == dataset.shape[0] assert Mb[i] <= MMb assert dataset.shape[1] == 3 - DEF[i,:Mb[i]] = dataset - self.print(f'mat {i}, Mb={Mb[i]}, DEF={DEF[i,:Mb[i]]}') + DEF[i, :Mb[i]] = dataset + self.print(f'mat {i}, Mb={Mb[i]}, DEF={DEF[i, :Mb[i]]}') h5f.close() self.DEF = DEF @@ -138,18 +138,18 @@ def load_h5_data(self): self._load_abc() def _load_abc(self): - #calculate ABC nodes (exterior of grid) + # calculate ABC nodes (exterior of grid) Nx = self.Nx Ny = self.Ny Nz = self.Nz Nba = 2*(Nx*Ny+Nx*Nz+Ny*Nz) - 12*(Nx+Ny+Nz) + 56 if self.fcc: Nba = Nba//2 - Q_bna = np.full((Nba,),0,dtype=np.int8) - bna_ixyz = np.full((Nba,),0,dtype=np.int64) - #get indices - nb_get_abc_ib(bna_ixyz,Q_bna,Nx,Ny,Nz,self.fcc) - #assert np.union1d(self.bn_ixyz,bna_ixyz).size == self.bn_ixyz.size + bna_ixyz.size + Q_bna = np.full((Nba,), 0, dtype=np.int8) + bna_ixyz = np.full((Nba,), 0, dtype=np.int64) + # get indices + nb_get_abc_ib(bna_ixyz, Q_bna, Nx, Ny, Nz, self.fcc) + # assert np.union1d(self.bn_ixyz,bna_ixyz).size == self.bn_ixyz.size + bna_ixyz.size self.Q_bna = Q_bna self.bna_ixyz = bna_ixyz self.Nba = Nba @@ -164,25 +164,25 @@ def allocate_mem(self): Ny = self.Ny Nz = self.Nz - u0 = np.zeros((Nx,Ny,Nz),dtype=np.float64) - u1 = np.zeros((Nx,Ny,Nz),dtype=np.float64) - Lu1 = np.zeros((Nx,Ny,Nz),dtype=np.float64) #laplacian applied to u1 + u0 = np.zeros((Nx, Ny, Nz), dtype=np.float64) + u1 = np.zeros((Nx, Ny, Nz), dtype=np.float64) + Lu1 = np.zeros((Nx, Ny, Nz), dtype=np.float64) # laplacian applied to u1 - u_out = np.zeros((Nr,Nt),dtype=np.float64) + u_out = np.zeros((Nr, Nt), dtype=np.float64) - Nbl = self.bnl_ixyz.size #reduced (non-rigid only) - u2b = np.zeros((Nbl,),dtype=np.float64) - u2ba = np.zeros((self.Nba,),dtype=np.float64) + Nbl = self.bnl_ixyz.size # reduced (non-rigid only) + u2b = np.zeros((Nbl,), dtype=np.float64) + u2ba = np.zeros((self.Nba,), dtype=np.float64) - vh0 = np.zeros((Nbl,MMb),dtype=np.float64) - vh1 = np.zeros((Nbl,MMb),dtype=np.float64) - gh1 = np.zeros((Nbl,MMb),dtype=np.float64) + vh0 = np.zeros((Nbl, MMb), dtype=np.float64) + vh1 = np.zeros((Nbl, MMb), dtype=np.float64) + gh1 = np.zeros((Nbl, MMb), dtype=np.float64) if self.energy_on: - self.H_tot = np.zeros((Nt,),dtype=np.float64) - self.E_lost = np.zeros((Nt+1,),dtype=np.float64) - self.E_in = np.zeros((Nt+1,),dtype=np.float64) - self.u2in = np.zeros((self.Ns,),dtype=np.float64) + self.H_tot = np.zeros((Nt,), dtype=np.float64) + self.E_lost = np.zeros((Nt+1,), dtype=np.float64) + self.E_in = np.zeros((Nt+1,), dtype=np.float64) + self.u2in = np.zeros((self.Ns,), dtype=np.float64) self.u_out = u_out self.u0 = u0 @@ -201,7 +201,7 @@ def setup_mask(self): Nz = self.Nz bn_ixyz = self.bn_ixyz - bn_mask = np.full((Nx,Ny,Nz),False) + bn_mask = np.full((Nx, Ny, Nz), False) bn_mask.flat[bn_ixyz] = True self.bn_mask = bn_mask @@ -215,46 +215,46 @@ def set_coeffs(self): Nm = self.Nm if self.fcc: - assert l2<=1.0 - a1 = 2.0-l2*3.0 #0.25*12 + assert l2 <= 1.0 + a1 = 2.0-l2*3.0 # 0.25*12 a2 = 0.25*l2 else: - assert l2<=1/3 + assert l2 <= 1/3 a1 = 2.0-l2*6.0 a2 = l2 - av = np.array([a1,a2],dtype=np.float64) - #'b' here means premultiplied by b, +1 material for rigid (and extra coeffs for energy) - mat_coeffs_struct = np.zeros((Nm+1,),dtype = [('b',np.float64,(MMb,)),\ - ('bd',np.float64,(MMb,)),\ - ('bDh',np.float64,(MMb,)),\ - ('bFh',np.float64,(MMb,)),\ - ('beta',np.float64),\ - ('D',np.float64,(MMb,)),\ - ('E',np.float64,(MMb,)),\ - ('F',np.float64,(MMb,))]) - assert np.all(mat_bnl=0) + assert np.all(mat_coeffs_struct['beta'] >= 0) if self.energy_on: - #copy for continguous memory access + # copy for continguous memory access self.D_bnl = np.copy(mat_coeffs_struct[mat_bnl]['D']) - self.E_bnl = np.copy(mat_coeffs_struct[mat_bnl]['E']) #this picks up fake rigid (zeros) + self.E_bnl = np.copy(mat_coeffs_struct[mat_bnl]['E']) # this picks up fake rigid (zeros) self.F_bnl = np.copy(mat_coeffs_struct[mat_bnl]['F']) self.av = av @@ -284,11 +284,11 @@ def set_coeffs(self): def checks(self): saf_bnl = self.saf_bnl if self.fcc: - assert np.all(saf_bnl<=12) #unscaled + assert np.all(saf_bnl <= 12) # unscaled else: - assert np.all(saf_bnl<=6) + assert np.all(saf_bnl <= 6) - def run_all(self,nsteps=1): + def run_all(self, nsteps=1): self.print('running..') Nx = self.Nx Ny = self.Ny @@ -298,25 +298,25 @@ def run_all(self,nsteps=1): timer = TimerDict() pbar = {} - pbar['vox']= tqdm(total=Nt*Npts,desc='FDTD run',unit='vox',unit_scale=True,ascii=True,leave=False,position=0,dynamic_ncols=True) - pbar['samples'] = tqdm(total=Nt,desc='FDTD run',unit='samples',unit_scale=True,ascii=True,leave=False,position=1,ncols=0) + pbar['vox'] = tqdm(total=Nt*Npts, desc='FDTD run', unit='vox', unit_scale=True, ascii=True, leave=False, position=0, dynamic_ncols=True) + pbar['samples'] = tqdm(total=Nt, desc='FDTD run', unit='samples', unit_scale=True, ascii=True, leave=False, position=1, ncols=0) timer.tic('run') - for n in range(0,Nt,nsteps): - nrun = min(nsteps,Nt-n) + for n in range(0, Nt, nsteps): + nrun = min(nsteps, Nt-n) - self.run_steps(n,nrun) + self.run_steps(n, nrun) pbar['vox'].update(Npts*nrun) pbar['samples'].update(nrun) - t_elapsed = timer.toc('run',print_elapsed=False) + t_elapsed = timer.toc('run', print_elapsed=False) pbar['vox'].close() pbar['samples'].close() self.print(f'Run-time loop: {t_elapsed:.6f}, {Nt*Npts/1e6/t_elapsed:.2f} MVox/s') - def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): + def run_plot(self, nsteps=1, draw_backend='mayavi', json_model=None): self.print('running..') Nx = self.Nx Ny = self.Ny @@ -324,16 +324,16 @@ def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): Nt = self.Nt bn_mask = self.bn_mask in_ixyz = self.in_ixyz - ix,iy,iz = ind2sub3d(in_ixyz,Nx,Ny,Nz) + ix, iy, iz = ind2sub3d(in_ixyz, Nx, Ny, Nz) iz_in = np.int_(np.median(iz)) ix_in = np.int_(np.median(ix)) iy_in = np.int_(np.median(iy)) xv = self.xv yv = self.yv zv = self.zv - bnm_xy = bn_mask[:,:,iz_in] - bnm_xz = bn_mask[:,iy_in,:] - bnm_yz = bn_mask[ix_in,:,:] + bnm_xy = bn_mask[:, :, iz_in] + bnm_xz = bn_mask[:, iy_in, :] + bnm_yz = bn_mask[ix_in, :, :] uxy = self.gather_slice(iz=iz_in) uxz = self.gather_slice(iy=iy_in) @@ -341,46 +341,46 @@ def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): xy_x, xy_y = np.meshgrid(xv, yv, indexing='xy') xz_x, xz_z = np.meshgrid(xv, zv, indexing='xy') yz_y, yz_z = np.meshgrid(yv, zv, indexing='xy') - if draw_backend=='matplotlib': + if draw_backend == 'matplotlib': import matplotlib.pyplot as plt - #initialise subplots + # initialise subplots fig = plt.figure() ax = fig.add_subplot(1, 3, 1) ax.set_title('xy-plane') ax.set_xlabel('x') ax.set_ylabel('y') - extent=[xv[0],xv[-1],yv[0],yv[-1],] - hh_xy = ax.imshow(uxy.T,extent=extent,origin='lower',aspect='equal') - ax.imshow(~bnm_xy.T,extent=extent,origin='lower',aspect='equal',alpha=np.float_(bnm_xy).T) + extent = [xv[0], xv[-1], yv[0], yv[-1],] + hh_xy = ax.imshow(uxy.T, extent=extent, origin='lower', aspect='equal') + ax.imshow(~bnm_xy.T, extent=extent, origin='lower', aspect='equal', alpha=np.float_(bnm_xy).T) fig.colorbar(hh_xy) ms = 4 - co = (0,0,0) - #marker boundary points - plt.plot(xy_x.flat[bnm_xy.T.flat[:]],xy_y.flat[bnm_xy.T.flat[:]],marker='.',markersize=ms,linestyle='none',color=co) + co = (0, 0, 0) + # marker boundary points + plt.plot(xy_x.flat[bnm_xy.T.flat[:]], xy_y.flat[bnm_xy.T.flat[:]], marker='.', markersize=ms, linestyle='none', color=co) ax = fig.add_subplot(1, 3, 2) ax.set_title('xz-plane') ax.set_xlabel('x') ax.set_ylabel('z') - extent=[xv[0],xv[-1],zv[0],zv[-1],] - hh_xz = ax.imshow(uxz.T,extent=extent,origin='lower',aspect='equal') + extent = [xv[0], xv[-1], zv[0], zv[-1],] + hh_xz = ax.imshow(uxz.T, extent=extent, origin='lower', aspect='equal') fig.colorbar(hh_xz) - plt.plot(xz_x.flat[bnm_xz.T.flat[:]],xz_z.flat[bnm_xz.T.flat[:]],marker='.',markersize=ms,linestyle='none',color=co) + plt.plot(xz_x.flat[bnm_xz.T.flat[:]], xz_z.flat[bnm_xz.T.flat[:]], marker='.', markersize=ms, linestyle='none', color=co) ax = fig.add_subplot(1, 3, 3) ax.set_title('yz-plane') ax.set_xlabel('y') ax.set_ylabel('z') - extent=[yv[0],yv[-1],zv[0],zv[-1],] - hh_yz = ax.imshow(uyz.T,extent=extent,origin='lower',aspect='equal') + extent = [yv[0], yv[-1], zv[0], zv[-1],] + hh_yz = ax.imshow(uyz.T, extent=extent, origin='lower', aspect='equal') fig.colorbar(hh_yz) - plt.plot(yz_y.flat[bnm_yz.T.flat[:]],yz_z.flat[bnm_yz.T.flat[:]],marker='.',markersize=ms,linestyle='none',color=co) + plt.plot(yz_y.flat[bnm_yz.T.flat[:]], yz_z.flat[bnm_yz.T.flat[:]], marker='.', markersize=ms, linestyle='none', color=co) plt.draw() - elif draw_backend=='mayavi': + elif draw_backend == 'mayavi': from mayavi import mlab from tvtk.api import tvtk @@ -391,75 +391,74 @@ def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): mats_dict = json_data['mats_hash'] mat_str = list(mats_dict.keys()) - #for section cuts - edges_xy_0 = np.array([[],[]]).T - edges_xy_1 = np.array([[],[]]).T - edges_xz_0 = np.array([[],[]]).T - edges_xz_1 = np.array([[],[]]).T - edges_yz_0 = np.array([[],[]]).T - edges_yz_1 = np.array([[],[]]).T + # for section cuts + edges_xy_0 = np.array([[], []]).T + edges_xy_1 = np.array([[], []]).T + edges_xz_0 = np.array([[], []]).T + edges_xz_1 = np.array([[], []]).T + edges_yz_0 = np.array([[], []]).T + edges_yz_1 = np.array([[], []]).T for mat in mat_str: - pts = np.array(mats_dict[mat]['pts'],dtype=np.float64) #no rotation - tris = np.array(mats_dict[mat]['tris'],dtype=np.int64) + pts = np.array(mats_dict[mat]['pts'], dtype=np.float64) # no rotation + tris = np.array(mats_dict[mat]['tris'], dtype=np.int64) for j in range(3): - ii = np.nonzero((pts[tris[:,j%3],2]>zv[iz_in]) ^ (pts[tris[:,(j+1)%3],2]>zv[iz_in]))[0] - edges_xy_0 = np.r_[edges_xy_0,pts[tris[ii,j%3]][:,[0,1]]] - edges_xy_1 = np.r_[edges_xy_1,pts[tris[ii,(j+1)%3]][:,[0,1]]] + ii = np.nonzero((pts[tris[:, j % 3], 2] > zv[iz_in]) ^ (pts[tris[:, (j+1) % 3], 2] > zv[iz_in]))[0] + edges_xy_0 = np.r_[edges_xy_0, pts[tris[ii, j % 3]][:, [0, 1]]] + edges_xy_1 = np.r_[edges_xy_1, pts[tris[ii, (j+1) % 3]][:, [0, 1]]] del ii - ii = np.nonzero((pts[tris[:,j%3],1]>yv[iy_in]) ^ (pts[tris[:,(j+1)%3],1]>yv[iy_in]))[0] - edges_xz_0 = np.r_[edges_xz_0,pts[tris[ii,j%3]][:,[0,2]]] - edges_xz_1 = np.r_[edges_xz_1,pts[tris[ii,(j+1)%3]][:,[0,2]]] + ii = np.nonzero((pts[tris[:, j % 3], 1] > yv[iy_in]) ^ (pts[tris[:, (j+1) % 3], 1] > yv[iy_in]))[0] + edges_xz_0 = np.r_[edges_xz_0, pts[tris[ii, j % 3]][:, [0, 2]]] + edges_xz_1 = np.r_[edges_xz_1, pts[tris[ii, (j+1) % 3]][:, [0, 2]]] del ii - ii = np.nonzero((pts[tris[:,j%3],0]>xv[ix_in]) ^ (pts[tris[:,(j+1)%3],0]>xv[ix_in]))[0] - edges_yz_0 = np.r_[edges_yz_0,pts[tris[ii,j%3]][:,[1,2]]] - edges_yz_1 = np.r_[edges_yz_1,pts[tris[ii,(j+1)%3]][:,[1,2]]] + ii = np.nonzero((pts[tris[:, j % 3], 0] > xv[ix_in]) ^ (pts[tris[:, (j+1) % 3], 0] > xv[ix_in]))[0] + edges_yz_0 = np.r_[edges_yz_0, pts[tris[ii, j % 3]][:, [1, 2]]] + edges_yz_1 = np.r_[edges_yz_1, pts[tris[ii, (j+1) % 3]][:, [1, 2]]] del ii - u = edges_xy_1[:,0]-edges_xy_0[:,0] - v = edges_xy_1[:,1]-edges_xy_0[:,1] - w=np.zeros(u.shape) - mlab.quiver3d(edges_xy_0[:,0],edges_xy_0[:,1],np.full(u.shape,zv[iz_in]),u,v,w,mode='2ddash',scale_factor=1.,color=(0,0,0)) + u = edges_xy_1[:, 0]-edges_xy_0[:, 0] + v = edges_xy_1[:, 1]-edges_xy_0[:, 1] + w = np.zeros(u.shape) + mlab.quiver3d(edges_xy_0[:, 0], edges_xy_0[:, 1], np.full(u.shape, zv[iz_in]), u, v, w, mode='2ddash', scale_factor=1., color=(0, 0, 0)) - u = edges_xz_1[:,0]-edges_xz_0[:,0] - v=np.zeros(u.shape) - w = edges_xz_1[:,1]-edges_xz_0[:,1] - mlab.quiver3d(edges_xz_0[:,0],np.full(u.shape,yv[iy_in]),edges_xz_0[:,1],u,v,w,mode='2ddash',scale_factor=1.,color=(0,0,0)) + u = edges_xz_1[:, 0]-edges_xz_0[:, 0] + v = np.zeros(u.shape) + w = edges_xz_1[:, 1]-edges_xz_0[:, 1] + mlab.quiver3d(edges_xz_0[:, 0], np.full(u.shape, yv[iy_in]), edges_xz_0[:, 1], u, v, w, mode='2ddash', scale_factor=1., color=(0, 0, 0)) - v = edges_yz_1[:,0]-edges_yz_0[:,0] - w = edges_yz_1[:,1]-edges_yz_0[:,1] - u=np.zeros(w.shape) - mlab.quiver3d(np.full(u.shape,xv[ix_in]),edges_yz_0[:,0],edges_yz_0[:,1],u,v,w,mode='2ddash',scale_factor=1.,color=(0,0,0)) + v = edges_yz_1[:, 0]-edges_yz_0[:, 0] + w = edges_yz_1[:, 1]-edges_yz_0[:, 1] + u = np.zeros(w.shape) + mlab.quiver3d(np.full(u.shape, xv[ix_in]), edges_yz_0[:, 0], edges_yz_0[:, 1], u, v, w, mode='2ddash', scale_factor=1., color=(0, 0, 0)) - mlab.triangular_mesh(*(pts.T),tris,color=(1., 1., 1.),opacity=0.2) - #mlab.triangular_mesh(*(pts.T),tris,color=(0., 0., 0.),representation='wireframe',opacity=0.2) + mlab.triangular_mesh(*(pts.T), tris, color=(1., 1., 1.), opacity=0.2) + # mlab.triangular_mesh(*(pts.T),tris,color=(0., 0., 0.),representation='wireframe',opacity=0.2) + # https://matplotlib.org/stable/tutorials/colors/colormaps.html - #https://matplotlib.org/stable/tutorials/colors/colormaps.html + # cmap = 'Greys' #(sequential) + # ldraw = lambda u : 20*np.log10(np.abs(u)+np.spacing(1)) - #cmap = 'Greys' #(sequential) - #ldraw = lambda u : 20*np.log10(np.abs(u)+np.spacing(1)) + cmap = 'seismic' # best to diff input so symmetric (diverging colormap) + def ldraw(u): return u - cmap = 'seismic' #best to diff input so symmetric (diverging colormap) - ldraw = lambda u : u + hh_xy = mlab.mesh(xy_x, xy_y, np.full(xy_x.shape, zv[iz_in]), scalars=ldraw(uxy.T), colormap=cmap) + hh_xz = mlab.mesh(xz_x, np.full(xz_x.shape, yv[iy_in]), xz_z, scalars=ldraw(uxz.T), colormap=cmap) + hh_yz = mlab.mesh(np.full(yz_y.shape, xv[ix_in]), yz_y, yz_z, scalars=ldraw(uyz.T), colormap=cmap) - hh_xy = mlab.mesh(xy_x,xy_y,np.full(xy_x.shape,zv[iz_in]),scalars=ldraw(uxy.T),colormap=cmap) - hh_xz = mlab.mesh(xz_x,np.full(xz_x.shape,yv[iy_in]),xz_z,scalars=ldraw(uxz.T),colormap=cmap) - hh_yz = mlab.mesh(np.full(yz_y.shape,xv[ix_in]),yz_y,yz_z,scalars=ldraw(uyz.T),colormap=cmap) - - #need three colorbars, but we only see one - mcb_xy = mlab.colorbar(object=hh_xy,orientation='vertical') #not sure if global scaling - mcb_xz = mlab.colorbar(object=hh_xz,orientation='vertical') #not sure if global scaling - mcb_yz = mlab.colorbar(object=hh_yz,orientation='vertical') #not sure if global scaling + # need three colorbars, but we only see one + mcb_xy = mlab.colorbar(object=hh_xy, orientation='vertical') # not sure if global scaling + mcb_xz = mlab.colorbar(object=hh_xz, orientation='vertical') # not sure if global scaling + mcb_yz = mlab.colorbar(object=hh_yz, orientation='vertical') # not sure if global scaling mlab.orientation_axes() - fake_verts = np.c_[np.array([xv[0],yv[0],zv[0]]),np.array([xv[-1],yv[-1],zv[-1]])] - mlab.plot3d(*fake_verts,transparent=True,opacity=0) + fake_verts = np.c_[np.array([xv[0], yv[0], zv[0]]), np.array([xv[-1], yv[-1], zv[-1]])] + mlab.plot3d(*fake_verts, transparent=True, opacity=0) mlab.axes(xlabel='x', ylabel='y', zlabel='z', color=(0., 0., 0.)) - #fix z-up + # fix z-up fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain() pbar = tqdm( @@ -473,37 +472,37 @@ def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): ncols=0, ) - for n in range(0,Nt,nsteps): - nrun = min(nsteps,Nt-n) - self.run_steps(n,nrun) + for n in range(0, Nt, nsteps): + nrun = min(nsteps, Nt-n) + self.run_steps(n, nrun) pbar.update(nrun) uxy = self.gather_slice(iz=iz_in) uxz = self.gather_slice(iy=iy_in) uyz = self.gather_slice(ix=ix_in) - if draw_backend=='matplotlib': + if draw_backend == 'matplotlib': cmax = np.max(np.abs(uxy.flat[:])) hh_xy.set_data(uxy.T) - hh_xy.set_clim(vmin=-cmax*1.1,vmax=cmax*1.1) + hh_xy.set_clim(vmin=-cmax*1.1, vmax=cmax*1.1) cmax = np.max(np.abs(uxz.flat[:])) hh_xz.set_data(uxz.T) - hh_xz.set_clim(vmin=-cmax*1.1,vmax=cmax*1.1) + hh_xz.set_clim(vmin=-cmax*1.1, vmax=cmax*1.1) cmax = np.max(np.abs(uyz.flat[:])) hh_yz.set_data(uyz.T) - hh_yz.set_clim(vmin=-cmax*1.1,vmax=cmax*1.1) + hh_yz.set_clim(vmin=-cmax*1.1, vmax=cmax*1.1) plt.draw() plt.pause(1e-10) - if len(plt.get_fignums())==0: + if len(plt.get_fignums()) == 0: break - elif draw_backend=='mayavi': + elif draw_backend == 'mayavi': cmax_xy = np.max(np.abs(uxy.flat[:])) cmax_xz = np.max(np.abs(uxz.flat[:])) cmax_yz = np.max(np.abs(uyz.flat[:])) - cmax = np.max(np.array([cmax_xy,cmax_yz,cmax_xz])) + cmax = np.max(np.array([cmax_xy, cmax_yz, cmax_xz])) hh_xy.mlab_source.scalars = ldraw(uxy.T) hh_xy.mlab_source.update() @@ -514,26 +513,26 @@ def run_plot(self,nsteps=1,draw_backend='mayavi',json_model=None): hh_yz.mlab_source.scalars = ldraw(uyz.T) hh_yz.mlab_source.update() - #mcb_xy.data_range = (cmax-40, cmax) - #mcb_yz.data_range = (cmax-40, cmax) - #mcb_xz.data_range = (cmax-40, cmax) + # mcb_xy.data_range = (cmax-40, cmax) + # mcb_yz.data_range = (cmax-40, cmax) + # mcb_xz.data_range = (cmax-40, cmax) mcb_xy.data_range = (-cmax*1.1, cmax*1.1) mcb_yz.data_range = (-cmax*1.1, cmax*1.1) mcb_xz.data_range = (-cmax*1.1, cmax*1.1) - #update + # update mlab.draw() time.sleep(1e-10) mlab.process_ui_events() - if draw_backend=='matplotlib': + if draw_backend == 'matplotlib': plt.close() - elif draw_backend=='mayavi': + elif draw_backend == 'mayavi': mlab.close() pbar.close() - def run_steps(self,nstart,nsteps): + def run_steps(self, nstart, nsteps): u0 = self.u0 u1 = self.u1 Lu1 = self.Lu1 @@ -581,57 +580,58 @@ def run_steps(self,nstart,nsteps): if self.fcc: nb_stencil_air = nb_stencil_air_fcc nb_stencil_bn = nb_stencil_bn_fcc - V_fac = 2.0 #cell-vol/h^3 + V_fac = 2.0 # cell-vol/h^3 else: nb_stencil_air = nb_stencil_air_cart nb_stencil_bn = nb_stencil_bn_cart - V_fac = 1.0 #cell-vol /h^3 + V_fac = 1.0 # cell-vol /h^3 - #run N steps (one at a time, in blocks, or full sim -- for port) - for n in range(nstart,nstart+nsteps): + # run N steps (one at a time, in blocks, or full sim -- for port) + for n in range(nstart, nstart+nsteps): if energy_on: u2 = self.u0 Lu2 = self.Lu1 u2in[:] = u0.flat[in_ixyz] - #NB: this is an 'energy-like' quantity, but not necessarily in Joules (off by ρ for u as velocity potential) - H_tot[n] = V_fac*0.5*h*nb_energy_int(u1,u2,Lu2,l2) #H_tot[n] = V_fac*0.5*h*np.sum((((u1-u2)**2)/l2 - u1*Lu2)[1:Nx-1,1:Ny-1,1:Nz-1]) - H_tot[n] -= V_fac*0.5*h*np.sum((1.0-V_bna)*(((u1.flat[bna_ixyz]-u2.flat[bna_ixyz])**2)/l2 - u1.flat[bna_ixyz]*Lu2.flat[bna_ixyz])) - #H_tot[n] -= V_fac*0.5*h*nb_energy_int_corr(V_bna,u1,u2,Lu2,l2,bna_ixyz) #problem with numba fn signature + # NB: this is an 'energy-like' quantity, but not necessarily in Joules (off by ρ for u as velocity potential) + H_tot[n] = V_fac*0.5*h*nb_energy_int(u1, u2, Lu2, l2) # H_tot[n] = V_fac*0.5*h*np.sum((((u1-u2)**2)/l2 - u1*Lu2)[1:Nx-1,1:Ny-1,1:Nz-1]) + H_tot[n] -= V_fac*0.5*h*np.sum((1.0-V_bna)*(((u1.flat[bna_ixyz]-u2.flat[bna_ixyz])**2)/l2 - u1.flat[bna_ixyz]*Lu2.flat[bna_ixyz])) + # H_tot[n] -= V_fac*0.5*h*nb_energy_int_corr(V_bna,u1,u2,Lu2,l2,bna_ixyz) #problem with numba fn signature - H_tot[n] += V_fac*0.5*c/l2*nb_energy_stored(ssaf_bnl,vh1,D_bnl,gh1,F_bnl,Ts) #H_tot[n] += V_fac*0.5*c/l2*np.sum(ssaf_bnl*((vh1**2)*D_bnl + ((Ts*gh1)**2)*F_bnl).T) + H_tot[n] += V_fac*0.5*c/l2*nb_energy_stored(ssaf_bnl, vh1, D_bnl, gh1, F_bnl, Ts) # H_tot[n] += V_fac*0.5*c/l2*np.sum(ssaf_bnl*((vh1**2)*D_bnl + ((Ts*gh1)**2)*F_bnl).T) - nb_save_bn(u0,u2ba,bna_ixyz) + nb_save_bn(u0, u2ba, bna_ixyz) nb_flip_halos(u1) - nb_stencil_air(Lu1,u1,bn_mask) - nb_stencil_bn(Lu1,u1,bn_ixyz,adj_bn) - nb_save_bn(u0,u2b,bnl_ixyz) - nb_leapfrog_update(u0,u1,Lu1,l2) - nb_update_bnl_fd(u0,u2b,l,bnl_ixyz,ssaf_bnl,vh0,vh1,gh1,mat_bnl,mat_coeffs_struct) + nb_stencil_air(Lu1, u1, bn_mask) + nb_stencil_bn(Lu1, u1, bn_ixyz, adj_bn) + nb_save_bn(u0, u2b, bnl_ixyz) + nb_leapfrog_update(u0, u1, Lu1, l2) + nb_update_bnl_fd(u0, u2b, l, bnl_ixyz, ssaf_bnl, vh0, vh1, gh1, mat_bnl, mat_coeffs_struct) - nb_update_abc(u0,u2ba,l,bna_ixyz,Q_bna) + nb_update_abc(u0, u2ba, l, bna_ixyz, Q_bna) - #inout - u0.flat[in_ixyz] += in_sigs[:,n] - u_out[:,n] = u1.flat[out_ixyz.flat[:]] + # inout + u0.flat[in_ixyz] += in_sigs[:, n] + u_out[:, n] = u1.flat[out_ixyz.flat[:]] if energy_on: - E_lost[n+1] = E_lost[n] + V_fac*0.25*h/l*nb_energy_loss(ssaf_bnl,vh0,vh1,E_bnl) #E_lost[n+1] = E_lost[n] + V_fac*0.25*h/l*np.sum(ssaf_bnl*(((vh0+vh1)**2)*E_bnl).T) + E_lost[n+1] = E_lost[n] + V_fac*0.25*h/l*nb_energy_loss(ssaf_bnl, vh0, vh1, E_bnl) # E_lost[n+1] = E_lost[n] + V_fac*0.25*h/l*np.sum(ssaf_bnl*(((vh0+vh1)**2)*E_bnl).T) - E_lost[n+1] += 0.5*V_fac*h/l*np.sum((V_bna*Q_bna)*(u0.flat[bna_ixyz]-u2ba)**2) #E_lost[n+1] += 0.5*V_fac*h/l*nb_energy_loss_abc(V_bna,Q_bna,u0,u2ba,bna_ixyz) #problem with numba fn signature - #H_tot[n] += E_lost[n] + # E_lost[n+1] += 0.5*V_fac*h/l*nb_energy_loss_abc(V_bna,Q_bna,u0,u2ba,bna_ixyz) #problem with numba fn signature + E_lost[n+1] += 0.5*V_fac*h/l*np.sum((V_bna*Q_bna)*(u0.flat[bna_ixyz]-u2ba)**2) + # H_tot[n] += E_lost[n] - E_in[n+1] = E_in[n] + (V_fac*h/l2)*0.5*np.sum((u0.flat[in_ixyz]-u2in)*in_sigs[:,n]) #have to undo (l2/h/V_fac) scaling applied to in_sigs + E_in[n+1] = E_in[n] + (V_fac*h/l2)*0.5*np.sum((u0.flat[in_ixyz]-u2in)*in_sigs[:, n]) # have to undo (l2/h/V_fac) scaling applied to in_sigs - u0,u1 = u1,u0 - vh0,vh1 = vh1,vh0 + u0, u1 = u1, u0 + vh0, vh1 = vh1, vh0 - #need to rebind these + # need to rebind these self.u0 = u0 self.u1 = u1 - self.Lu1 = Lu1 #reuse for energy + self.Lu1 = Lu1 # reuse for energy self.vh0 = vh0 self.vh1 = vh1 @@ -643,274 +643,283 @@ def run_steps(self,nstart,nsteps): self.E_lost = E_lost self.E_in = E_in - def gather_slice(self,ix=None,iy=None,iz=None): + def gather_slice(self, ix=None, iy=None, iz=None): u1 = self.u1 if ix is not None: - uslice = u1[ix,:,:] - #fill in checkerboard effect if FCC (subgrid) + uslice = u1[ix, :, :] + # fill in checkerboard effect if FCC (subgrid) if self.fcc: - nb_fcc_fill_plot_holes(uslice,ix) + nb_fcc_fill_plot_holes(uslice, ix) elif iy is not None: - uslice = u1[:,iy,:] + uslice = u1[:, iy, :] if self.fcc: - nb_fcc_fill_plot_holes(uslice,iy) + nb_fcc_fill_plot_holes(uslice, iy) elif iz is not None: - uslice = u1[:,:,iz] + uslice = u1[:, :, iz] if self.fcc: - nb_fcc_fill_plot_holes(uslice,iz) + nb_fcc_fill_plot_holes(uslice, iz) return uslice - def print_last_samples(self,Np): + def print_last_samples(self, Np): self.print('GRID OUTPUTS') u_out = self.u_out out_reorder = self.out_reorder Nt = self.Nt Nr = self.Nr - for i in range(0,Nr): + for i in range(0, Nr): self.print(f'out {i}') - for n in range(Nt-Np,Nt): - self.print(f'sample {n}: {u_out[out_reorder[i],n]:.16e}') + for n in range(Nt-Np, Nt): + self.print(f'sample {n}: {u_out[out_reorder[i], n]:.16e}') - def print_last_energy(self,Np): + def print_last_energy(self, Np): self.print('ENERGY') H_tot = self.H_tot E_lost = self.E_lost E_in = self.E_in Nt = self.Nt - for n in range(Nt-Np,Nt): - self.print(f'normalised energy balance:{rel_diff(H_tot[n]+E_lost[n],E_in[n]):.16e}') + for n in range(Nt-Np, Nt): + self.print(f'normalised energy balance:{rel_diff(H_tot[n]+E_lost[n], E_in[n]):.16e}') - #fig = plt.figure() - #ax = fig.add_subplot(1, 1, 1) - #for i in range(-20,20,1): - #ax.axhline(i*np.spacing(1), linestyle='-', color=(0.9,0.9,0.9)) - #ax.plot(rel_diff(H_tot+E_lost[:-1],E_in[:-1]),linestyle='None',marker='.') - #ax.grid(which='both', axis='both') - #plt.show() + # fig = plt.figure() + # ax = fig.add_subplot(1, 1, 1) + # for i in range(-20,20,1): + # ax.axhline(i*np.spacing(1), linestyle='-', color=(0.9,0.9,0.9)) + # ax.plot(rel_diff(H_tot+E_lost[:-1],E_in[:-1]),linestyle='None',marker='.') + # ax.grid(which='both', axis='both') + # plt.show() def save_outputs(self): sim_dir = self.sim_dir u_out = self.u_out out_reorder = self.out_reorder - #just raw outputs, recombine elsewhere - h5f = h5py.File(sim_dir / Path('sim_outs.h5'),'w') - h5f.create_dataset('u_out', data=u_out[out_reorder,:]) + # just raw outputs, recombine elsewhere + h5f = h5py.File(sim_dir / Path('sim_outs.h5'), 'w') + h5f.create_dataset('u_out', data=u_out[out_reorder, :]) h5f.close() self.print('saved outputs in {sim_dir}') -@nb.jit(nopython=True,parallel=True) -def nb_stencil_air_cart(Lu1,u1,bn_mask): - Nx,Ny,Nz = u1.shape - for ix in nb.prange(1,Nx-1): - for iy in range(1,Ny-1): - for iz in range(1,Nz-1): - if not bn_mask[ix,iy,iz]: - Lu1[ix,iy,iz] = -6.0*u1[ix,iy,iz] \ - + u1[ix+1,iy,iz] \ - + u1[ix-1,iy,iz] \ - + u1[ix,iy+1,iz] \ - + u1[ix,iy-1,iz] \ - + u1[ix,iy,iz+1] \ - + u1[ix,iy,iz-1] - -@nb.jit(nopython=True,parallel=True) -def nb_stencil_air_fcc(Lu1,u1,bn_mask): - Nx,Ny,Nz = u1.shape - for ix in nb.prange(1,Nx-1): - for iy in range(1,Ny-1): - for iz in range(1,Nz-1): - if (np.mod(ix+iy+iz,2)==0) and (not bn_mask[ix,iy,iz]): - Lu1[ix,iy,iz] = 0.25*(-12.0*u1[ix,iy,iz] \ - + u1[ix+1,iy+1,iz] \ - + u1[ix-1,iy-1,iz] \ - + u1[ix,iy+1,iz+1] \ - + u1[ix,iy-1,iz-1] \ - + u1[ix+1,iy,iz+1] \ - + u1[ix-1,iy,iz-1] \ - + u1[ix+1,iy-1,iz] \ - + u1[ix-1,iy+1,iz] \ - + u1[ix,iy+1,iz-1] \ - + u1[ix,iy-1,iz+1] \ - + u1[ix+1,iy,iz-1] \ - + u1[ix-1,iy,iz+1]) - -@nb.jit(nopython=True,parallel=True) -def nb_stencil_bn_fcc(Lu1,u1,bn_ixyz,adj_bn): - _,Ny,Nz = u1.shape + +@nb.jit(nopython=True, parallel=True) +def nb_stencil_air_cart(Lu1, u1, bn_mask): + Nx, Ny, Nz = u1.shape + for ix in nb.prange(1, Nx-1): + for iy in range(1, Ny-1): + for iz in range(1, Nz-1): + if not bn_mask[ix, iy, iz]: + Lu1[ix, iy, iz] = -6.0*u1[ix, iy, iz] \ + + u1[ix+1, iy, iz] \ + + u1[ix-1, iy, iz] \ + + u1[ix, iy+1, iz] \ + + u1[ix, iy-1, iz] \ + + u1[ix, iy, iz+1] \ + + u1[ix, iy, iz-1] + + +@nb.jit(nopython=True, parallel=True) +def nb_stencil_air_fcc(Lu1, u1, bn_mask): + Nx, Ny, Nz = u1.shape + for ix in nb.prange(1, Nx-1): + for iy in range(1, Ny-1): + for iz in range(1, Nz-1): + if (np.mod(ix+iy+iz, 2) == 0) and (not bn_mask[ix, iy, iz]): + Lu1[ix, iy, iz] = 0.25*(-12.0*u1[ix, iy, iz] + + u1[ix+1, iy+1, iz] + + u1[ix-1, iy-1, iz] + + u1[ix, iy+1, iz+1] + + u1[ix, iy-1, iz-1] + + u1[ix+1, iy, iz+1] + + u1[ix-1, iy, iz-1] + + u1[ix+1, iy-1, iz] + + u1[ix-1, iy+1, iz] + + u1[ix, iy+1, iz-1] + + u1[ix, iy-1, iz+1] + + u1[ix+1, iy, iz-1] + + u1[ix-1, iy, iz+1]) + + +@nb.jit(nopython=True, parallel=True) +def nb_stencil_bn_fcc(Lu1, u1, bn_ixyz, adj_bn): + _, Ny, Nz = u1.shape Nb = bn_ixyz.size for i in nb.prange(Nb): - K = np.sum(adj_bn[i,:]) + K = np.sum(adj_bn[i, :]) ib = bn_ixyz[i] - Lu1.flat[ib] = 0.25*(-K*u1.flat[ib] \ - + adj_bn[i,0] * u1.flat[ib+Ny*Nz+Nz ] \ - + adj_bn[i,1] * u1.flat[ib-Ny*Nz-Nz ] \ - + adj_bn[i,2] * u1.flat[ib+Nz+1] \ - + adj_bn[i,3] * u1.flat[ib-Nz-1] \ - + adj_bn[i,4] * u1.flat[ib+Ny*Nz+1] \ - + adj_bn[i,5] * u1.flat[ib-Ny*Nz-1] \ - + adj_bn[i,6] * u1.flat[ib+Ny*Nz-Nz ] \ - + adj_bn[i,7] * u1.flat[ib-Ny*Nz+Nz ] \ - + adj_bn[i,8] * u1.flat[ib+Nz-1] \ - + adj_bn[i,9] * u1.flat[ib-Nz+1] \ - + adj_bn[i,10]* u1.flat[ib+Ny*Nz-1] \ - + adj_bn[i,11]* u1.flat[ib-Ny*Nz+1]) - - -@nb.jit(nopython=True,parallel=True) -def nb_stencil_bn_cart(Lu1,u1,bn_ixyz,adj_bn): - _,Ny,Nz = u1.shape + Lu1.flat[ib] = 0.25*(-K*u1.flat[ib] + + adj_bn[i, 0] * u1.flat[ib+Ny*Nz+Nz] + + adj_bn[i, 1] * u1.flat[ib-Ny*Nz-Nz] + + adj_bn[i, 2] * u1.flat[ib+Nz+1] + + adj_bn[i, 3] * u1.flat[ib-Nz-1] + + adj_bn[i, 4] * u1.flat[ib+Ny*Nz+1] + + adj_bn[i, 5] * u1.flat[ib-Ny*Nz-1] + + adj_bn[i, 6] * u1.flat[ib+Ny*Nz-Nz] + + adj_bn[i, 7] * u1.flat[ib-Ny*Nz+Nz] + + adj_bn[i, 8] * u1.flat[ib+Nz-1] + + adj_bn[i, 9] * u1.flat[ib-Nz+1] + + adj_bn[i, 10] * u1.flat[ib+Ny*Nz-1] + + adj_bn[i, 11] * u1.flat[ib-Ny*Nz+1]) + + +@nb.jit(nopython=True, parallel=True) +def nb_stencil_bn_cart(Lu1, u1, bn_ixyz, adj_bn): + _, Ny, Nz = u1.shape Nb = bn_ixyz.size for i in nb.prange(Nb): - K = np.sum(adj_bn[i,:]) + K = np.sum(adj_bn[i, :]) ib = bn_ixyz[i] - Lu1.flat[ib] = -K*u1.flat[ib]\ - + adj_bn[i,0]*u1.flat[ib+Ny*Nz]\ - + adj_bn[i,1]*u1.flat[ib-Ny*Nz]\ - + adj_bn[i,2]*u1.flat[ib+Nz]\ - + adj_bn[i,3]*u1.flat[ib-Nz]\ - + adj_bn[i,4]*u1.flat[ib+1]\ - + adj_bn[i,5]*u1.flat[ib-1] - -@nb.jit(nopython=True,parallel=True) + Lu1.flat[ib] = -K*u1.flat[ib]\ + + adj_bn[i, 0]*u1.flat[ib+Ny*Nz]\ + + adj_bn[i, 1]*u1.flat[ib-Ny*Nz]\ + + adj_bn[i, 2]*u1.flat[ib+Nz]\ + + adj_bn[i, 3]*u1.flat[ib-Nz]\ + + adj_bn[i, 4]*u1.flat[ib+1]\ + + adj_bn[i, 5]*u1.flat[ib-1] + + +@nb.jit(nopython=True, parallel=True) def nb_flip_halos(u1): - Nx,Ny,Nz = u1.shape + Nx, Ny, Nz = u1.shape for ix in nb.prange(Nx): for iy in range(Ny): - u1[ix,iy,0] = u1[ix,iy,2] - u1[ix,iy,Nz-1] = u1[ix,iy,Nz-3] + u1[ix, iy, 0] = u1[ix, iy, 2] + u1[ix, iy, Nz-1] = u1[ix, iy, Nz-3] for ix in nb.prange(Nx): for iz in range(Nz): - u1[ix,0,iz] = u1[ix,2,iz] - u1[ix,Ny-1,iz] = u1[ix,Ny-3,iz] + u1[ix, 0, iz] = u1[ix, 2, iz] + u1[ix, Ny-1, iz] = u1[ix, Ny-3, iz] for iy in nb.prange(Ny): for iz in range(Nz): - u1[0,iy,iz] = u1[2,iy,iz] - u1[Nx-1,iy,iz] = u1[Nx-3,iy,iz] + u1[0, iy, iz] = u1[2, iy, iz] + u1[Nx-1, iy, iz] = u1[Nx-3, iy, iz] -@nb.jit(nopython=True,parallel=True) -def nb_save_bn(u0,u2b,bn_ixyz): - #using for bnl and bna +@nb.jit(nopython=True, parallel=True) +def nb_save_bn(u0, u2b, bn_ixyz): + # using for bnl and bna Nb = bn_ixyz.size for i in nb.prange(Nb): ib = bn_ixyz[i] - u2b.flat[i] = u0.flat[ib] #save before overwrite - -@nb.jit(nopython=True,parallel=True) -def nb_leapfrog_update(u0,u1,Lu1,l2): - Nx,Ny,Nz = u0.shape - for ix in nb.prange(1,Nx-1): - for iy in range(1,Ny-1): - for iz in range(1,Nz-1): - u0[ix,iy,iz] = 2.0*u1[ix,iy,iz] - u0[ix,iy,iz] + l2*Lu1[ix,iy,iz] - -@nb.jit(nopython=True,parallel=True) -def nb_update_abc(u0,u2ba,l,bna_ixyz,Q_bna): + u2b.flat[i] = u0.flat[ib] # save before overwrite + + +@nb.jit(nopython=True, parallel=True) +def nb_leapfrog_update(u0, u1, Lu1, l2): + Nx, Ny, Nz = u0.shape + for ix in nb.prange(1, Nx-1): + for iy in range(1, Ny-1): + for iz in range(1, Nz-1): + u0[ix, iy, iz] = 2.0*u1[ix, iy, iz] - u0[ix, iy, iz] + l2*Lu1[ix, iy, iz] + + +@nb.jit(nopython=True, parallel=True) +def nb_update_abc(u0, u2ba, l, bna_ixyz, Q_bna): Nba = bna_ixyz.size for i in nb.prange(Nba): lQ = l*Q_bna[i] ib = bna_ixyz[i] u0.flat[ib] = (u0.flat[ib] + lQ*u2ba[i])/(1.0 + lQ) -@nb.jit(nopython=True,parallel=True) -def nb_update_bnl_fd(u0,u2b,l,bnl_ixyz,ssaf_bnl,vh0,vh1,gh1,mat_bnl,mat_coeffs_struct): + +@nb.jit(nopython=True, parallel=True) +def nb_update_bnl_fd(u0, u2b, l, bnl_ixyz, ssaf_bnl, vh0, vh1, gh1, mat_bnl, mat_coeffs_struct): Nbl = bnl_ixyz.size for i in nb.prange(Nbl): k = mat_bnl[i] - if k==-1: #shouldn't happen, but leaving as reminder + if k == -1: # shouldn't happen, but leaving as reminder continue - b = mat_coeffs_struct[k]['b'] - bd = mat_coeffs_struct[k]['bd'] + b = mat_coeffs_struct[k]['b'] + bd = mat_coeffs_struct[k]['bd'] bDh = mat_coeffs_struct[k]['bDh'] bFh = mat_coeffs_struct[k]['bFh'] beta = mat_coeffs_struct[k]['beta'] - lo2Kbg = 0.5*l*ssaf_bnl[i]*beta #has fcc scaling + lo2Kbg = 0.5*l*ssaf_bnl[i]*beta # has fcc scaling ib = bnl_ixyz[i] - ##add branches - u0.flat[ib] -= l*ssaf_bnl[i]*np.sum(2.0*bDh*vh1[i,:]-bFh*gh1[i,:]) + # add branches + u0.flat[ib] -= l*ssaf_bnl[i]*np.sum(2.0*bDh*vh1[i, :]-bFh*gh1[i, :]) u0.flat[ib] = (u0.flat[ib] + lo2Kbg*u2b[i])/(1.0 + lo2Kbg) - ##update temp variables (for loop implicit) - vh0[i,:] = b*(u0.flat[ib]-u2b[i]) + bd*vh1[i,:] - 2.0*bFh*gh1[i,:] - gh1[i,:] += 0.5*vh0[i,:] + 0.5*vh1[i,:] + # update temp variables (for loop implicit) + vh0[i, :] = b*(u0.flat[ib]-u2b[i]) + bd*vh1[i, :] - 2.0*bFh*gh1[i, :] + gh1[i, :] += 0.5*vh0[i, :] + 0.5*vh1[i, :] -@nb.jit(nopython=True,parallel=True) -def nb_energy_int(u1,u2,Lu2,l2): - Nx,Ny,Nz = u1.shape - return np.sum((((u1-u2)**2)/l2 - u1*Lu2)[1:Nx-1,1:Ny-1,1:Nz-1]) - #psum = 0.0 - #for i in nb.prange(u1.size): - #psum += ((u1.flat[i]-u2.flat[i])**2)/l2 - u1.flat[i]*Lu2.flat[i] - #return psum +@nb.jit(nopython=True, parallel=True) +def nb_energy_int(u1, u2, Lu2, l2): + Nx, Ny, Nz = u1.shape + return np.sum((((u1-u2)**2)/l2 - u1*Lu2)[1:Nx-1, 1:Ny-1, 1:Nz-1]) + # psum = 0.0 + # for i in nb.prange(u1.size): + # psum += ((u1.flat[i]-u2.flat[i])**2)/l2 - u1.flat[i]*Lu2.flat[i] + # return psum -@nb.jit(nopython=True,parallel=True) -def nb_energy_stored(ssaf_bnl,vh1,D_bnl,gh1,F_bnl,Ts): +@nb.jit(nopython=True, parallel=True) +def nb_energy_stored(ssaf_bnl, vh1, D_bnl, gh1, F_bnl, Ts): return np.sum(ssaf_bnl*((vh1**2)*D_bnl + ((Ts*gh1)**2)*F_bnl).T) -@nb.jit(nopython=True,parallel=True) -def nb_energy_loss(ssaf_bnl,vh0,vh1,E_bnl): + +@nb.jit(nopython=True, parallel=True) +def nb_energy_loss(ssaf_bnl, vh0, vh1, E_bnl): return np.sum(ssaf_bnl*(((vh0+vh1)**2)*E_bnl).T) -#@nb.jit(nopython=True,parallel=True) -#def nb_energy_loss_abc(V_bna,Q_bna,u0,u2ba,bna_ixyz): - #return np.sum((V_bna*Q_bna)*(u0.flat[bna_ixyz]-u2ba)**2) +# @nb.jit(nopython=True,parallel=True) +# def nb_energy_loss_abc(V_bna,Q_bna,u0,u2ba,bna_ixyz): + # return np.sum((V_bna*Q_bna)*(u0.flat[bna_ixyz]-u2ba)**2) + +# @nb.jit(nopython=True,parallel=True) +# def nb_energy_int_corr(V_bna,u1,u2,Lu2,l2,bna_ixyz): + # return np.sum((1.0-V_bna)*(((u1.flat[bna_ixyz]-u2.flat[bna_ixyz])**2)/l2 - u1.flat[bna_ixyz]*Lu2.flat[bna_ixyz])) -#@nb.jit(nopython=True,parallel=True) -#def nb_energy_int_corr(V_bna,u1,u2,Lu2,l2,bna_ixyz): - #return np.sum((1.0-V_bna)*(((u1.flat[bna_ixyz]-u2.flat[bna_ixyz])**2)/l2 - u1.flat[bna_ixyz]*Lu2.flat[bna_ixyz])) -@nb.jit(nopython=True,parallel=False) -def nb_get_abc_ib(bna_ixyz,Q_bna,Nx,Ny,Nz,fcc): +@nb.jit(nopython=True, parallel=False) +def nb_get_abc_ib(bna_ixyz, Q_bna, Nx, Ny, Nz, fcc): ii = 0 - #just doing naive full pass - for ix in range(1,Nx-1): - for iy in range(1,Ny-1): - for iz in range(1,Nz-1): - if fcc and (ix+iy+iz)%2==1: + # just doing naive full pass + for ix in range(1, Nx-1): + for iy in range(1, Ny-1): + for iz in range(1, Nz-1): + if fcc and (ix+iy+iz) % 2 == 1: continue Q = 0 if ix in (1, Nx-2): - Q+=1 + Q += 1 if iy in (1, Ny-2): - Q+=1 + Q += 1 if iz in (1, Nz-2): - Q+=1 - if Q>0: + Q += 1 + if Q > 0: bna_ixyz[ii] = ix*Ny*Nz + iy*Nz + iz Q_bna[ii] = Q ii += 1 - assert ii==bna_ixyz.size + assert ii == bna_ixyz.size -@nb.jit(nopython=True,parallel=True) -def nb_fcc_fill_plot_holes(uslice,i3): - N1,N2 = uslice.shape - for i1 in nb.prange(1,N1-1): - for i2 in range(1,N2-1): - if (i1+i2+i3)%2==1: - uslice[i1,i2] = 0.25*(uslice[i1+1,i2] + uslice[i1-1,i2] + uslice[i1,i2+1] + uslice[i1,i2-1]) +@nb.jit(nopython=True, parallel=True) +def nb_fcc_fill_plot_holes(uslice, i3): + N1, N2 = uslice.shape + for i1 in nb.prange(1, N1-1): + for i2 in range(1, N2-1): + if (i1+i2+i3) % 2 == 1: + uslice[i1, i2] = 0.25*(uslice[i1+1, i2] + uslice[i1-1, i2] + uslice[i1, i2+1] + uslice[i1, i2-1]) def main(): import argparse parser = argparse.ArgumentParser() - parser.add_argument('--sim_dir', type=str,help='run directory') - parser.add_argument('--json_model', type=str,help='json to plot section cuts') - parser.add_argument('--plot', action='store_true',help='plot 2d slice') - parser.add_argument('--draw_backend',type=str,help='matplotlib or mayavi') - parser.add_argument('--energy', action='store_true',help='do energy calc') - parser.add_argument('--nsteps', type=int,help='run in batches of steps (less frequent progress)') - parser.add_argument('--nthreads', type=int,help='number of threads for parallel execution') - parser.add_argument('--abc', action='store_true',help='apply ABCs') + parser.add_argument('--sim_dir', type=str, help='run directory') + parser.add_argument('--json_model', type=str, help='json to plot section cuts') + parser.add_argument('--plot', action='store_true', help='plot 2d slice') + parser.add_argument('--draw_backend', type=str, help='matplotlib or mayavi') + parser.add_argument('--energy', action='store_true', help='do energy calc') + parser.add_argument('--nsteps', type=int, help='run in batches of steps (less frequent progress)') + parser.add_argument('--nthreads', type=int, help='number of threads for parallel execution') + parser.add_argument('--abc', action='store_true', help='apply ABCs') parser.set_defaults(draw_backend='matplotlib') parser.set_defaults(plot=False) parser.set_defaults(energy=False) @@ -923,11 +932,11 @@ def main(): args = parser.parse_args() if args.json_model is not None: - assert args.draw_backend=='mayavi' + assert args.draw_backend == 'mayavi' - eng = EnginePython3D(args.sim_dir,energy_on=args.energy,nthreads=args.nthreads) + eng = EnginePython3D(args.sim_dir, energy_on=args.energy, nthreads=args.nthreads) if args.plot: - eng.run_plot(draw_backend=args.draw_backend,json_model=args.json_model) + eng.run_plot(draw_backend=args.draw_backend, json_model=args.json_model) else: eng.run_all(args.nsteps) eng.save_outputs() @@ -936,5 +945,6 @@ def main(): if args.energy: eng.print_last_energy(5) + if __name__ == '__main__': main() diff --git a/src/python/pffdtd/sim3d/model_builder.py b/src/python/pffdtd/sim3d/model_builder.py index 103323f..68c3b9f 100644 --- a/src/python/pffdtd/sim3d/model_builder.py +++ b/src/python/pffdtd/sim3d/model_builder.py @@ -17,16 +17,16 @@ def load_mesh(obj_file, reverse=False): pts = [] for line in lines: - if not "vn " in line: - if "v " in line: - parts = line.split(" ") + if not 'vn ' in line: + if 'v ' in line: + parts = line.split(' ') parts = parts[1:4] parts = [float(part)/1000.0 for part in parts] pts.append(parts) - if "f " in line: - parts = line.split(" ") + if 'f ' in line: + parts = line.split(' ') parts = parts[1:4] - parts = [int(part.split("/")[0])-1 for part in parts] + parts = [int(part.split('/')[0])-1 for part in parts] tris.append(parts if not reverse else parts[::-1]) return pts, tris @@ -35,37 +35,37 @@ def load_mesh(obj_file, reverse=False): class MeshModelBuilder: def __init__(self) -> None: self.root = { - "mats_hash": {}, - "sources": [], - "receivers": [] + 'mats_hash': {}, + 'sources': [], + 'receivers': [] } def add(self, name, obj_file, color, reverse=False): assert name not in self.root pts, tris = load_mesh(obj_file, reverse=reverse) - self.root["mats_hash"][name] = { - "tris": tris, - "pts": pts, - "color": color, - "sides": [1]*len(tris) + self.root['mats_hash'][name] = { + 'tris': tris, + 'pts': pts, + 'color': color, + 'sides': [1]*len(tris) } def add_receiver(self, name, pos): - self.root["receivers"].append({"name": name, "xyz": pos}) + self.root['receivers'].append({'name': name, 'xyz': pos}) def add_source(self, name, pos): - self.root["sources"].append({"name": name, "xyz": pos}) + self.root['sources'].append({'name': name, 'xyz': pos}) def write(self, model_file): - src = np.array(self.root["sources"][0]["xyz"]) - ref = np.linalg.norm(src - np.array(self.root["receivers"][0]["xyz"])) - for r in self.root["receivers"]: - distance = np.linalg.norm(src - np.array(r["xyz"])) - print(r["name"], 20*np.log10(ref/distance)) + src = np.array(self.root['sources'][0]['xyz']) + ref = np.linalg.norm(src - np.array(self.root['receivers'][0]['xyz'])) + for r in self.root['receivers']: + distance = np.linalg.norm(src - np.array(r['xyz'])) + print(r['name'], 20*np.log10(ref/distance)) - with open(model_file, "w") as f: + with open(model_file, 'w') as f: json.dump(self.root, f) - print("", file=f) + print('', file=f) class RoomModelBuilder: @@ -90,8 +90,8 @@ def with_colors(self, colors): def add_source(self, name, position): self.sources.append({ - "name": name, - "xyz": position, + 'name': name, + 'xyz': position, }) return self @@ -102,25 +102,25 @@ def add_cabinet_speaker(self, name, position, size=None, center=None): self.add_source(name, position) self.cabinet_sources.append({ - "xyz": position, - "size": size, - "center": center, + 'xyz': position, + 'size': size, + 'center': center, }) return self def add_receiver(self, name, position): self.receivers.append({ - "name": name, - "xyz": position, + 'name': name, + 'xyz': position, }) return self def add_box(self, material, size, position, rotation=None): self.boxes[material].append({ - "size": size, - "position": position, - "rotation": rotation if rotation else [0, 0, 0], + 'size': size, + 'position': position, + 'rotation': rotation if rotation else [0, 0, 0], }) return self @@ -133,7 +133,7 @@ def add_diffusor_1d(self, size, position, well_width): y = position[1] z = position[2] depth = depths[i % len(depths)] - self.add_box("Diffusor", [well_width, depth, size[2]], [x, y, z]) + self.add_box('Diffusor', [well_width, depth, size[2]], [x, y, z]) def build(self, file_path): L = self.length @@ -143,16 +143,16 @@ def build(self, file_path): counter = 0 for src in self.cabinet_sources: rot = [0, 0, 0] - size = src["size"] - xyz = src["xyz"] - center = src["center"] + size = src['size'] + xyz = src['xyz'] + center = src['center'] pos = list(np.array(xyz) - np.array(center)) - self.add_box("Speaker_Cabinet", size, pos, rot) + self.add_box('Speaker_Cabinet', size, pos, rot) model = { - "mats_hash": { - "Walls": { - "tris": [ + 'mats_hash': { + 'Walls': { + 'tris': [ [0, 1, 2], # Back Wall [0, 2, 3], @@ -165,7 +165,7 @@ def build(self, file_path): [5, 2, 1], # Right Wall [2, 5, 6], ], - "pts": [ + 'pts': [ [0.0, 0.0, 0.0], # Back Wall [W, 0.0, 0.0], [W, 0.0, H], @@ -176,59 +176,59 @@ def build(self, file_path): [W, L, H], [0.0, L, H], ], - "color": self.get_color("Walls"), - "sides": [1, 1, 1, 1, 1, 1, 1, 1] + 'color': self.get_color('Walls'), + 'sides': [1, 1, 1, 1, 1, 1, 1, 1] }, - "Ceiling": { - "tris": [[2, 1, 0], [2, 3, 1]], - "pts": [ + 'Ceiling': { + 'tris': [[2, 1, 0], [2, 3, 1]], + 'pts': [ [0, 0, H], [0, L, H], [W, 0, H], [W, L, H] ], - "color": self.get_color("Ceiling"), - "sides": [1, 1] + 'color': self.get_color('Ceiling'), + 'sides': [1, 1] }, - "Floor": { - "tris": [[0, 1, 2], [1, 3, 2]], - "pts": [ + 'Floor': { + 'tris': [[0, 1, 2], [1, 3, 2]], + 'pts': [ [0, 0, 0], [0, L, 0], [W, 0, 0], [W, L, 0] ], - "color": self.get_color("Floor"), - "sides": [1, 1] + 'color': self.get_color('Floor'), + 'sides': [1, 1] }, }, - "sources": self.sources, - "receivers": self.receivers, + 'sources': self.sources, + 'receivers': self.receivers, } for key, boxes in self.boxes.items(): spec = { - "tris": [], - "pts": [], - "color": self.get_color(key), - "sides": [] + 'tris': [], + 'pts': [], + 'color': self.get_color(key), + 'sides': [] } counter = 0 for box in boxes: - size = box["size"] - pos = box["position"] - rot = box["rotation"] + size = box['size'] + pos = box['position'] + rot = box['rotation'] ps, ts = make_box(size[0], size[1], size[2], pos, rot, counter) - spec["tris"] += ts - spec["pts"] += ps - spec["sides"] += [2]*len(ts) + spec['tris'] += ts + spec['pts'] += ps + spec['sides'] += [2]*len(ts) counter += len(ps) - model["mats_hash"][key] = spec + model['mats_hash'][key] = spec - with open(file_path, "w") as file: + with open(file_path, 'w') as file: json.dump(model, file) - print("", file=file) + print('', file=file) diff --git a/src/python/pffdtd/sim3d/process_outputs.py b/src/python/pffdtd/sim3d/process_outputs.py index bb3683c..225c124 100644 --- a/src/python/pffdtd/sim3d/process_outputs.py +++ b/src/python/pffdtd/sim3d/process_outputs.py @@ -300,7 +300,7 @@ def process_outputs( po.show_plots() -@click.command(name="process-outputs", help="Process raw simulation output.") +@click.command(name='process-outputs', help='Process raw simulation output.') @click.option('--sim_dir', type=click.Path(exists=True)) @click.option('--plot', is_flag=True) @click.option('--plot_raw', is_flag=True) @@ -311,7 +311,7 @@ def process_outputs( @click.option('--order_lowcut', default=8) @click.option('--order_lowpass', default=8) @click.option('--symmetric_lowpass', is_flag=True) -@click.option('--air_abs_filter', default="none") +@click.option('--air_abs_filter', default='none') def main(sim_dir, plot, plot_raw, save_wav, resample_fs, fcut_lowcut, fcut_lowpass, order_lowcut, order_lowpass, symmetric_lowpass, air_abs_filter): process_outputs( sim_dir=sim_dir, diff --git a/src/python/pffdtd/sim3d/room_geometry.py b/src/python/pffdtd/sim3d/room_geometry.py index a6531a1..d641909 100644 --- a/src/python/pffdtd/sim3d/room_geometry.py +++ b/src/python/pffdtd/sim3d/room_geometry.py @@ -6,9 +6,10 @@ import numpy as np from numpy import array as npa -from pffdtd.geometry.math import dotv,rotate_az_el_deg +from pffdtd.geometry.math import dotv, rotate_az_el_deg from pffdtd.geometry.tris_precompute import tris_precompute + class RoomGeometry: """Class for room geometry, source/receiver positions, and materials (labels) @@ -16,17 +17,17 @@ class RoomGeometry: - Also prunes triangles, prints some stats (surface areas, volume), rotates scene, and draws """ - def __init__(self,model_file=None,az_el=[0.,0.],area_eps=1e-6,bmin=None,bmax=None): - #main dict for room data + def __init__(self, model_file=None, az_el=[0., 0.], area_eps=1e-6, bmin=None, bmax=None): + # main dict for room data self.mats_dict = None - #bmin and bmax may take custom bounds of scene + # bmin and bmax may take custom bounds of scene if bmin is None: - self.bmin = npa([np.inf,np.inf,np.inf]) + self.bmin = npa([np.inf, np.inf, np.inf]) else: self.bmin = bmin if bmax is None: - self.bmax = -npa([np.inf,np.inf,np.inf]) + self.bmax = -npa([np.inf, np.inf, np.inf]) else: self.bmax = bmax @@ -42,63 +43,63 @@ def __init__(self,model_file=None,az_el=[0.,0.],area_eps=1e-6,bmin=None,bmax=Non raise self.area_eps = area_eps - #identity 3x3 matrix by default - self.R,_,_ = rotate_az_el_deg(*az_el) + # identity 3x3 matrix by default + self.R, _, _ = rotate_az_el_deg(*az_el) - if np.any(az_el!=0): + if np.any(az_el != 0): self.print(f'az-el deg rotation: {az_el}') self.load_json(model_file) self.collapse_tris() self.calc_volume() - def print(self,fstring): + def print(self, fstring): print(f'--ROOM_GEO: {fstring}') - def load_json(self,json_filename): + def load_json(self, json_filename): bmin = self.bmin bmax = self.bmax R = self.R with open(json_filename) as model_file: data = json.load(model_file) - #print(data) + # print(data) - #attach + # attach mats_dict = data['mats_hash'] mat_str = list(mats_dict.keys()) - Nmat = len(mat_str) #not including unmarked - mat_str.sort() #will process in alphabetical order + Nmat = len(mat_str) # not including unmarked + mat_str.sort() # will process in alphabetical order if '_RIGID' in mat_str: - #move to end (also corresponds to -1 index) + # move to end (also corresponds to -1 index) mat_str.remove('_RIGID') mat_str.append('_RIGID') - Nmat -= 1 #adjust Nmat + Nmat -= 1 # adjust Nmat - #print(mat_str) + # print(mat_str) colors = [] - #convert to np arrays + # convert to np arrays for mat in mat_str: - mats_dict[mat]['pts'] = npa(mats_dict[mat]['pts'],dtype=np.float64) @ R #also rotate pts here - mats_dict[mat]['tris'] = npa(mats_dict[mat]['tris'],dtype=np.int64) + mats_dict[mat]['pts'] = npa(mats_dict[mat]['pts'], dtype=np.float64) @ R # also rotate pts here + mats_dict[mat]['tris'] = npa(mats_dict[mat]['tris'], dtype=np.int64) colors.append(mats_dict[mat]['color']) - #calculate bmin/bmax + # calculate bmin/bmax for mat in mat_str: pts = mats_dict[mat]['pts'] tris = mats_dict[mat]['tris'] - bmin = np.min(np.r_[pts,bmin[None,:]],axis=0) - bmax = np.max(np.r_[pts,bmax[None,:]],axis=0) + bmin = np.min(np.r_[pts, bmin[None, :]], axis=0) + bmax = np.max(np.r_[pts, bmax[None, :]], axis=0) - assert len(data['sources'])>0 #sources have to be defined in JSON - assert len(data['receivers'])>0 #receivers have to be defined in JSON - Sxyz = np.atleast_2d(npa([source['xyz'] for source in data['sources']],dtype=np.float64)) @ R - assert np.all((Sxyz>bmin) & (Sxyz 0 # sources have to be defined in JSON + assert len(data['receivers']) > 0 # receivers have to be defined in JSON + Sxyz = np.atleast_2d(npa([source['xyz'] for source in data['sources']], dtype=np.float64)) @ R + assert np.all((Sxyz > bmin) & (Sxyz < bmax)) - Rxyz = np.atleast_2d(npa([receiver['xyz'] for receiver in data['receivers']],dtype=np.float64)) @ R - assert np.all((Rxyz>bmin) & (Rxyz bmin) & (Rxyz < bmax)) self.mats_dict = mats_dict self.mat_str = mat_str @@ -113,29 +114,29 @@ def collapse_tris(self): mats_dict = self.mats_dict mat_str = self.mat_str Nmat = self.Nmat - #collapse tris and pts for easier computation - pts = np.concatenate([mats_dict[mat]['pts'] for mat in mat_str],axis=0) + # collapse tris and pts for easier computation + pts = np.concatenate([mats_dict[mat]['pts'] for mat in mat_str], axis=0) - #need to offset pt indices when concatenating tris - tri_offsets = np.r_[0,np.cumsum([mats_dict[mat]['pts'].shape[0] for mat in mat_str])[:-1]] - assert tri_offsets.size == len(mat_str) #otherwise error, until PEP618 (Python 3.10) - tris = np.concatenate([mats_dict[mat]['tris']+toff for mat,toff in zip(mat_str,tri_offsets)],axis=0) + # need to offset pt indices when concatenating tris + tri_offsets = np.r_[0, np.cumsum([mats_dict[mat]['pts'].shape[0] for mat in mat_str])[:-1]] + assert tri_offsets.size == len(mat_str) # otherwise error, until PEP618 (Python 3.10) + tris = np.concatenate([mats_dict[mat]['tris']+toff for mat, toff in zip(mat_str, tri_offsets)], axis=0) - #can handle open scenes, but expects exported JSON to have at least four triangles - assert tris.shape[0]>=4 #if this is a problem just insert tiny fake triangles into scene, or modify code + # can handle open scenes, but expects exported JSON to have at least four triangles + assert tris.shape[0] >= 4 # if this is a problem just insert tiny fake triangles into scene, or modify code - #use array of int8, -1 will be flag for unmarked (rigid) - mat_ind = np.concatenate([np.ones(mats_dict[mat]['tris'].shape[0],dtype=np.int8)*ind for mat,ind in zip(mat_str,range(len(mat_str)))],axis=0) - mat_ind[mat_ind==Nmat]=-1 #this should be anything on _RIGID (when len(mat_str)==Nmat+1) + # use array of int8, -1 will be flag for unmarked (rigid) + mat_ind = np.concatenate([np.ones(mats_dict[mat]['tris'].shape[0], dtype=np.int8)*ind for mat, ind in zip(mat_str, range(len(mat_str)))], axis=0) + mat_ind[mat_ind == Nmat] = -1 # this should be anything on _RIGID (when len(mat_str)==Nmat+1) - mat_side = np.concatenate([mats_dict[mat]['sides'] for mat in mat_str],axis=0) + mat_side = np.concatenate([mats_dict[mat]['sides'] for mat in mat_str], axis=0) - #all unmarked should have 0 for sidedness - assert np.all(mat_side[mat_ind==-1]==0) + # all unmarked should have 0 for sidedness + assert np.all(mat_side[mat_ind == -1] == 0) - #print(f'{pts=}') - #print(f'{tris=}') - tris_pre = tris_precompute(tris=tris,pts=pts) + # print(f'{pts=}') + # print(f'{tris=}') + tris_pre = tris_precompute(tris=tris, pts=pts) self.pts = pts self.tris = tris @@ -143,7 +144,7 @@ def collapse_tris(self): self.mat_side = mat_side self.tris_pre = tris_pre - self.prune_by_area() #delete small triangles for ray-tri or tri-box processing (not removing from original dict) + self.prune_by_area() # delete small triangles for ray-tri or tri-box processing (not removing from original dict) self.calc_areas() def calc_areas(self): @@ -152,105 +153,104 @@ def calc_areas(self): tris_pre = self.tris_pre Nmat = self.Nmat - mat_area = np.empty((Nmat,),np.float64) - for i in range(0,Nmat): #ignores unmarked - ii = np.nonzero(mat_ind==i)[0] + mat_area = np.empty((Nmat,), np.float64) + for i in range(0, Nmat): # ignores unmarked + ii = np.nonzero(mat_ind == i)[0] sides = mat_side[ii] fac = np.zeros(sides.shape) - fac[sides==1]=1.0 #back side only - fac[sides==2]=1.0 #front side only - fac[sides==3]=2.0 #both sides + fac[sides == 1] = 1.0 # back side only + fac[sides == 2] = 1.0 # front side only + fac[sides == 3] = 2.0 # both sides mat_area[i] = np.sum(tris_pre[ii]['area']*fac) self.mat_area = mat_area def prune_by_area(self): - ii = np.nonzero(self.tris_pre['area']=Ny/2) + bix, biy, biz = ind2sub3d(bn_ixyz, Nx, Ny, Nz) + ii = (biy >= Ny/2) - #bn_ixyz - bn_ixyz[ii] = np.c_[bix[ii],Ny-biy[ii]-1,biz[ii]] @ npa([Nz*Nyh,Nz,1]) - bn_ixyz[~ii] = np.c_[bix[~ii],biy[~ii],biz[~ii]] @ npa([Nz*Nyh,Nz,1]) + # bn_ixyz + bn_ixyz[ii] = np.c_[bix[ii], Ny-biy[ii]-1, biz[ii]] @ npa([Nz*Nyh, Nz, 1]) + bn_ixyz[~ii] = np.c_[bix[~ii], biy[~ii], biz[~ii]] @ npa([Nz*Nyh, Nz, 1]) - adj_bn[ii,0],adj_bn[ii,6] = adj_bn[ii,6],adj_bn[ii,0] - adj_bn[ii,1],adj_bn[ii,7] = adj_bn[ii,7],adj_bn[ii,1] - adj_bn[ii,2],adj_bn[ii,9] = adj_bn[ii,9],adj_bn[ii,2] - adj_bn[ii,3],adj_bn[ii,8] = adj_bn[ii,8],adj_bn[ii,3] + adj_bn[ii, 0], adj_bn[ii, 6] = adj_bn[ii, 6], adj_bn[ii, 0] + adj_bn[ii, 1], adj_bn[ii, 7] = adj_bn[ii, 7], adj_bn[ii, 1] + adj_bn[ii, 2], adj_bn[ii, 9] = adj_bn[ii, 9], adj_bn[ii, 2] + adj_bn[ii, 3], adj_bn[ii, 8] = adj_bn[ii, 8], adj_bn[ii, 3] - #in_ixyz - bix,biy,biz = ind2sub3d(in_ixyz,Nx,Ny,Nz) - ii = (biy>=Ny/2) - in_ixyz[ii] = np.c_[bix[ii],Ny-biy[ii]-1,biz[ii]] @ npa([Nz*Nyh,Nz,1]) - in_ixyz[~ii] = np.c_[bix[~ii],biy[~ii],biz[~ii]] @ npa([Nz*Nyh,Nz,1]) + # in_ixyz + bix, biy, biz = ind2sub3d(in_ixyz, Nx, Ny, Nz) + ii = (biy >= Ny/2) + in_ixyz[ii] = np.c_[bix[ii], Ny-biy[ii]-1, biz[ii]] @ npa([Nz*Nyh, Nz, 1]) + in_ixyz[~ii] = np.c_[bix[~ii], biy[~ii], biz[~ii]] @ npa([Nz*Nyh, Nz, 1]) - #out_ixyz - bix,biy,biz = ind2sub3d(out_ixyz,Nx,Ny,Nz) - ii = (biy>=Ny/2) - out_ixyz[ii] = np.c_[bix[ii],Ny-biy[ii]-1,biz[ii]] @ npa([Nz*Nyh,Nz,1]) - out_ixyz[~ii] = np.c_[bix[~ii],biy[~ii],biz[~ii]] @ npa([Nz*Nyh,Nz,1]) + # out_ixyz + bix, biy, biz = ind2sub3d(out_ixyz, Nx, Ny, Nz) + ii = (biy >= Ny/2) + out_ixyz[ii] = np.c_[bix[ii], Ny-biy[ii]-1, biz[ii]] @ npa([Nz*Nyh, Nz, 1]) + out_ixyz[~ii] = np.c_[bix[~ii], biy[~ii], biz[~ii]] @ npa([Nz*Nyh, Nz, 1]) timer.tic('write') - #write - h5f = h5py.File(sim_dir / Path('signals.h5'),'r+') + # write + h5f = h5py.File(sim_dir / Path('signals.h5'), 'r+') h5f['in_ixyz'][...] = in_ixyz h5f['out_ixyz'][...] = out_ixyz h5f.close() - h5f = h5py.File(sim_dir / Path('vox_out.h5'),'r+') + h5f = h5py.File(sim_dir / Path('vox_out.h5'), 'r+') h5f['bn_ixyz'][...] = bn_ixyz h5f['adj_bn'][...] = adj_bn h5f['Ny'][()] = Nyh h5f.close() - h5f = h5py.File(sim_dir / Path('constants.h5'),'r+') + h5f = h5py.File(sim_dir / Path('constants.h5'), 'r+') h5f['fcc_flag'][()] = 2 h5f.close() _print(timer.ftoc('write')) -def copy_sim_data(src_sim_dir,dst_sim_dir): + +def copy_sim_data(src_sim_dir, dst_sim_dir): def _print(fstring): print(f'--COPY DATA: {fstring}') src_sim_dir = Path(src_sim_dir) @@ -271,14 +275,14 @@ def _print(fstring): _print(f'copying {file}') shutil.copy(file, dst_sim_dir) -#def main(): - #import argparse - #parser = argparse.ArgumentParser() - #parser.add_argument('--sim_dir', type=str,help='run directory') - #parser.set_defaults(sim_dir=None) +# def main(): + # import argparse + # parser = argparse.ArgumentParser() + # parser.add_argument('--sim_dir', type=str,help='run directory') + # parser.set_defaults(sim_dir=None) # - #args = parser.parse_args() - #rotate(args.sim_dir) + # args = parser.parse_args() + # rotate(args.sim_dir) # -#if __name__ == '__main__': - #main() +# if __name__ == '__main__': + # main() diff --git a/src/python/pffdtd/sim3d/setup.py b/src/python/pffdtd/sim3d/setup.py index fe5fb0a..09b773e 100644 --- a/src/python/pffdtd/sim3d/setup.py +++ b/src/python/pffdtd/sim3d/setup.py @@ -3,9 +3,14 @@ """Function to set up a PFFDTD simulation with single source and multiple receivers """ - +import importlib +import importlib.util +import inspect from pathlib import Path +from typing import Literal +import uuid +import click import numpy as np from pffdtd.sim3d.constants import SimConstants @@ -20,31 +25,32 @@ def sim_setup_3d( # The following are required but using None default so not positional - insig_type=None, #sig type (see sig_comms.py) - fmax=None, #fmax for simulation (to set grid spacing) - PPW=None, #points per wavelength (also to set grid spacing) - save_folder=None, #where to save .h5 files - model_json_file=None, #json export of model - mat_folder=None, #folder where to find .h5 DEF coefficients for wal impedances - mat_files_dict=None, #dict to link up materials to .h5 mat files - duration=None, #duration to simulate, in seconds + insig_type=None, # sig type (see sig_comms.py) + fmax=None, # fmax for simulation (to set grid spacing) + PPW=None, # points per wavelength (also to set grid spacing) + save_folder=None, # where to save .h5 files + model_json_file=None, # json export of model + mat_folder=None, # folder where to find .h5 DEF coefficients for wal impedances + mat_files_dict=None, # dict to link up materials to .h5 mat files + duration=None, # duration to simulate, in seconds # The following are not required - Tc=20, #temperature in deg C (sets sound speed) - rh=50, #relative humidity of air (configures air absorption post processing) - source_num=1, #1-based indexing, source to simulate (in sources.csv) - save_folder_gpu=None, #folder to save gpu-prepared .h5 data (sorted and rotated and FCC-folded) - draw_vox=False, #draw voxelization - draw_backend='mayavi', #default, 'polyscope' better for larger grids - diff_source=False, #use this for single precision runs - fcc_flag=False, #to use FCC scheme - bmin=None, #to set custom scene bounds (useful for open scenes) - bmax=None, #to set custom scene bounds (useful for open scenes) - Nvox_est=None, #to manually set number of voxels (for ray-tri intersections) for voxelization - Nh=None, #to set voxel size in grid pacing (for ray-tri intersections) - Nprocs=None, #number of processes for multiprocessing, defaults to 80% of cores - compress=None, #GZIP compress for HDF5, 0 to 9 (fast to slow) - rot_az_el=[0.,0.], #to rotate the whole scene (including sources/receivers) -- to test robustness of scheme + Tc=20, # temperature in deg C (sets sound speed) + rh=50, # relative humidity of air (configures air absorption post processing) + source_num=1, # 1-based indexing, source to simulate (in sources.csv) + save_folder_gpu=None, # folder to save gpu-prepared .h5 data (sorted and rotated and FCC-folded) + draw_vox=False, # draw voxelization + draw_backend='mayavi', # default, 'polyscope' better for larger grids + diff_source=False, # use this for single precision runs + fcc_flag=False, # to use FCC scheme + bmin=None, # to set custom scene bounds (useful for open scenes) + bmax=None, # to set custom scene bounds (useful for open scenes) + Nvox_est=None, # to manually set number of voxels (for ray-tri intersections) for voxelization + Nh=None, # to set voxel size in grid pacing (for ray-tri intersections) + Nprocs=None, # number of processes for multiprocessing, defaults to 80% of cores + compress=None, # GZIP compress for HDF5, 0 to 9 (fast to slow) + rot_az_el=[0., 0.], # to rotate the whole scene (including sources/receivers) -- to test robustness of scheme + model_factory=None, ): assert Tc is not None assert rh is not None @@ -58,11 +64,18 @@ def sim_setup_3d( assert mat_files_dict is not None assert duration is not None + # some constants for the simulation, in one place + constants = SimConstants(Tc=Tc, rh=rh, fmax=fmax, PPW=PPW, fcc=fcc_flag) + constants.save(save_folder) + if (bmin is not None) and (bmax is not None): # custom bmin/bmax (for open scenes) bmin = np.array(bmin, dtype=np.float64) bmax = np.array(bmax, dtype=np.float64) + if model_factory: + model_factory(constants) + # set up room geometry (reads in JSON export, rotates scene) room_geo = RoomGeometry(model_json_file, az_el=rot_az_el, bmin=bmin, bmax=bmax) room_geo.print_stats() @@ -71,10 +84,6 @@ def sim_setup_3d( Sxyz = room_geo.Sxyz[source_num-1] # one source (one-based indexing) Rxyz = room_geo.Rxyz # many receivers - # some constants for the simulation, in one place - constants = SimConstants(Tc=Tc, rh=rh, fmax=fmax, PPW=PPW, fcc=fcc_flag) - constants.save(save_folder) - # link up the wall materials to impedance datasets materials = SimMaterials(save_folder=save_folder) materials.package(mat_files_dict=mat_files_dict, @@ -97,12 +106,12 @@ def sim_setup_3d( # set up the voxel grid (volume hierarchy for ray-triangle intersections) vox_grid = VoxGrid(room_geo, cart_grid, Nvox_est=Nvox_est, Nh=Nh) - vox_grid.fill(Nprocs=1) + vox_grid.fill(Nprocs=Nprocs) vox_grid.print_stats() # 'voxelize' the scene (calculate FDTD mesh adjacencies and identify/correct boundary surfaces) vox_scene = VoxScene(room_geo, cart_grid, vox_grid, fcc=fcc_flag) - vox_scene.calc_adj(Nprocs=1) + vox_scene.calc_adj(Nprocs=Nprocs) vox_scene.check_adj_full() vox_scene.save(save_folder, compress=compress) @@ -123,3 +132,74 @@ def sim_setup_3d( room_geo.draw(wireframe=False, backend=draw_backend) vox_scene.draw(backend=draw_backend) room_geo.show(backend=draw_backend) + + +class Setup3D: + duration: float + fmax: float + ppw: float + fcc: bool + Tc: float = 20 + rh: float = 50 + + model_file: str + bmin: list[float] | None = None + bmax: list[float] | None = None + rot_az_el: list[float] = [0.0, 0.0] + materials: dict[str, str] = {} + mat_folder: str | None = None + + source_index: int + source_signal: Literal['impulse', 'hann10'] + diff_source: bool = True + + compress: int = 0 + save_folder: str + save_folder_gpu: str | None + + draw_vox: bool = True + draw_backend: Literal['mayavi', 'polyscope'] = 'polyscope' + + +@click.command(name='setup', help='Generate simulation files.') +@click.argument('sim_file', nargs=1, type=click.Path(exists=True)) +def main(sim_file): + module_id = str(uuid.uuid1()) + spec = importlib.util.spec_from_file_location(module_id, sim_file) + loaded = importlib.util.module_from_spec(spec) + spec.loader.exec_module(loaded) + + for name, value in inspect.getmembers(loaded): + if inspect.isclass(value) and issubclass(value, Setup3D) and name != 'Setup3D': + sim = value() + model_factory = None + if hasattr(sim, 'generate_model'): + def model_factory(c): return sim.generate_model(c) + + sim_setup_3d( + insig_type=sim.source_signal, + fmax=sim.fmax, + PPW=sim.ppw, + save_folder=sim.save_folder, + model_json_file=sim.model_file, + mat_folder=sim.mat_folder, + mat_files_dict=sim.materials, + duration=sim.duration, + + Tc=sim.Tc, + rh=sim.rh, + source_num=sim.source_index, + save_folder_gpu=sim.save_folder_gpu, + draw_vox=sim.draw_vox, + draw_backend=sim.draw_backend, + diff_source=sim.diff_source, + fcc_flag=sim.fcc, + bmin=sim.bmin, + bmax=sim.bmax, + Nvox_est=None, + Nh=None, + Nprocs=None, + compress=sim.compress, + rot_az_el=sim.rot_az_el, + model_factory=model_factory, + ) diff --git a/src/python/pffdtd/sim3d/signals.py b/src/python/pffdtd/sim3d/signals.py index 806186f..2050116 100644 --- a/src/python/pffdtd/sim3d/signals.py +++ b/src/python/pffdtd/sim3d/signals.py @@ -5,127 +5,128 @@ import h5py import numpy as np -from numpy import pi,cos,sin -from scipy.signal import butter,lfilter, sosfilt +from numpy import pi, cos, sin +from scipy.signal import butter, lfilter, sosfilt from pffdtd.common.timerdict import TimerDict from pffdtd.geometry.math import iceil + class SimSignals: """Class for source/receiver positions and input signals """ - def __init__(self,save_folder): - #will read h,xv,yv,zv from h5 data + def __init__(self, save_folder): + # will read h,xv,yv,zv from h5 data save_folder = Path(save_folder) assert save_folder.exists() assert save_folder.is_dir() - h5f = h5py.File(save_folder / Path('constants.h5'),'r') - self.h = h5f['h'][()] - self.Ts = h5f['Ts'][()] - self.l2 = h5f['l2'][()] - self.fcc_flag = h5f['fcc_flag'][()] + h5f = h5py.File(save_folder / Path('constants.h5'), 'r') + self.h = h5f['h'][()] + self.Ts = h5f['Ts'][()] + self.l2 = h5f['l2'][()] + self.fcc_flag = h5f['fcc_flag'][()] h5f.close() - h5f = h5py.File(save_folder / Path('cart_grid.h5'),'r') - self.xv = h5f['xv'][()] - self.yv = h5f['yv'][()] - self.zv = h5f['zv'][()] + h5f = h5py.File(save_folder / Path('cart_grid.h5'), 'r') + self.xv = h5f['xv'][()] + self.yv = h5f['yv'][()] + self.zv = h5f['zv'][()] h5f.close() - self.fcc = (self.fcc_flag>0) + self.fcc = (self.fcc_flag > 0) if self.fcc: - assert self.xv.size%2 == 0 - assert self.yv.size%2 == 0 - assert self.zv.size%2 == 0 + assert self.xv.size % 2 == 0 + assert self.yv.size % 2 == 0 + assert self.zv.size % 2 == 0 self.save_folder = save_folder self._diff = False - def print(self,fstring): + def print(self, fstring): print(f'--SIGNALS: {fstring}') - def prepare_source_pts(self,Sxyz): - in_alpha,in_ixyz = self.get_linear_interp_weights(Sxyz) + def prepare_source_pts(self, Sxyz): + in_alpha, in_ixyz = self.get_linear_interp_weights(Sxyz) self.in_alpha = in_alpha self.in_ixyz = in_ixyz - #a few signals to choose from - def prepare_source_signals(self,duration,sig_type='impulse'): + # a few signals to choose from + def prepare_source_signals(self, duration, sig_type='impulse'): in_alpha = self.in_alpha Ts = self.Ts Nt = np.int_(np.ceil(duration/Ts)) - in_sigs = np.zeros((in_alpha.size,Nt),dtype=np.float64) + in_sigs = np.zeros((in_alpha.size, Nt), dtype=np.float64) in_sig = np.zeros((Nt,)) - if sig_type=='impulse': #for RIRs + if sig_type == 'impulse': # for RIRs in_sig[0] = 1.0 - if sig_type=='impulse-highpass': #for RIRs + if sig_type == 'impulse-highpass': # for RIRs in_sig[0] = 1.0 sos = butter(4, 40, 'highpass', fs=1/Ts, output='sos') in_sig = sosfilt(sos, in_sig) - elif sig_type=='hann10': #for viz + elif sig_type == 'hann10': # for viz N = 10 n = np.arange(N) in_sig[:N] = 0.5*(1.0-cos(2*pi*n/N)) - elif sig_type=='hann20': #for viz + elif sig_type == 'hann20': # for viz N = 20 n = np.arange(N) in_sig[:N] = 0.5*(1.0-cos(2*pi*n/N)) - elif sig_type=='dhann30': #symmetric, for viz + elif sig_type == 'dhann30': # symmetric, for viz N = 30 n = np.arange(N) in_sig[:N] = cos(pi*n/N)*sin(pi*n/N) - elif sig_type=='hann5ms': #for consistency checking + elif sig_type == 'hann5ms': # for consistency checking N = iceil(5e-3/Ts) n = np.arange(N) in_sig[:N] = 0.5*(1.0-cos(2*pi*n/N)) - in_sigs = in_alpha[:,None]*in_sig[None,:] + in_sigs = in_alpha[:, None]*in_sig[None, :] self.in_sigs = in_sigs self._scale_source_signals() - def _scale_source_signals(self): #scaling for FDTD sim + def _scale_source_signals(self): # scaling for FDTD sim l2 = self.l2 h = self.h in_sigs = self.in_sigs if self.fcc: - in_sigs *= 0.5*l2/h #c²Ts²/cell-vol + in_sigs *= 0.5*l2/h # c²Ts²/cell-vol else: - in_sigs *= l2/h #c²Ts²/cell-vol + in_sigs *= l2/h # c²Ts²/cell-vol self.in_sigs = in_sigs def diff_source(self): - #differentiate with bilinear transform, to undo after sim - #required for single precision (safeguard against DC instability) + # differentiate with bilinear transform, to undo after sim + # required for single precision (safeguard against DC instability) in_sigs = self.in_sigs Ts = self.Ts if self._diff: - return #do nothing if already applied + return # do nothing if already applied - b = 2/Ts*np.array([1.0,-1.0]) #don't need Ts scaling but simpler to keep for receiver post-processing - a = np.array([1.0,1.0]) - in_sigs = lfilter(b,a,in_sigs,axis=-1) + b = 2/Ts*np.array([1.0, -1.0]) # don't need Ts scaling but simpler to keep for receiver post-processing + a = np.array([1.0, 1.0]) + in_sigs = lfilter(b, a, in_sigs, axis=-1) self._diff = True self.in_sigs = in_sigs - def prepare_receiver_pts(self,Rxyz): + def prepare_receiver_pts(self, Rxyz): Rxyz = np.atleast_2d(Rxyz) - #many receivers, can have duplicates - out_alpha = np.zeros((Rxyz.shape[0],8),np.float64) - out_ixyz = np.zeros((Rxyz.shape[0],8),dtype=np.int64) + # many receivers, can have duplicates + out_alpha = np.zeros((Rxyz.shape[0], 8), np.float64) + out_ixyz = np.zeros((Rxyz.shape[0], 8), dtype=np.int64) for rr in range(Rxyz.shape[0]): - out_alpha[rr],out_ixyz[rr] = self.get_linear_interp_weights(Rxyz[rr]) + out_alpha[rr], out_ixyz[rr] = self.get_linear_interp_weights(Rxyz[rr]) self.out_alpha = out_alpha self.out_ixyz = out_ixyz - def save(self,save_folder=None,compress=None): + def save(self, save_folder=None, compress=None): if save_folder is None: save_folder = self.save_folder assert save_folder.exists() @@ -139,18 +140,18 @@ def save(self,save_folder=None,compress=None): assert save_folder.is_dir() in_ixyz = self.in_ixyz - in_alpha = self.in_alpha #don't need this anymore but update just in case + in_alpha = self.in_alpha # don't need this anymore but update just in case out_ixyz = self.out_ixyz.flat[:] out_alpha = self.out_alpha - #out_alpha = self.out_alpha.flat[:] - out_reorder = np.arange(out_ixyz.size) #no sorting here + # out_alpha = self.out_alpha.flat[:] + out_reorder = np.arange(out_ixyz.size) # no sorting here in_sigs = self.in_sigs if compress is not None: - kw = {'compression': "gzip", 'compression_opts': compress} + kw = {'compression': 'gzip', 'compression_opts': compress} else: kw = {} - h5f = h5py.File(save_folder / Path('signals.h5'),'w') + h5f = h5py.File(save_folder / Path('signals.h5'), 'w') h5f.create_dataset('in_ixyz', data=in_ixyz, **kw) h5f.create_dataset('out_ixyz', data=out_ixyz, **kw) h5f.create_dataset('out_alpha', data=out_alpha, **kw) @@ -162,84 +163,84 @@ def save(self,save_folder=None,compress=None): h5f.create_dataset('diff', data=np.int8(self._diff)) h5f.close() - #reattach updated values + # reattach updated values self.out_ixyz = out_ixyz self.out_alpha = out_alpha self.in_alpha = in_alpha self.in_ixyz = in_ixyz self.in_sigs = in_sigs - def get_linear_interp_weights(self,pos_xyz): + def get_linear_interp_weights(self, pos_xyz): h = self.h xv = self.xv yv = self.yv zv = self.zv - ix_iy_iz = np.empty(pos_xyz.shape,dtype=np.int64) + ix_iy_iz = np.empty(pos_xyz.shape, dtype=np.int64) Nx = xv.size Ny = yv.size Nz = zv.size - xyzv_list = [xv,yv,zv] + xyzv_list = [xv, yv, zv] alpha_xyz = np.zeros((3,)) - for j in [0,1,2]: - ix_iy_iz[j] = np.flatnonzero(xyzv_list[j]>=pos_xyz[j])[0] #return first item + for j in [0, 1, 2]: + ix_iy_iz[j] = np.flatnonzero(xyzv_list[j] >= pos_xyz[j])[0] # return first item alpha_xyz[j] = (xyzv_list[j][ix_iy_iz[j]] - pos_xyz[j])/h - #look back - ix_iy_iz8_off = np.array([[0,0,0],\ - [-1,0,0],\ - [0,-1,0],\ - [0,0,-1],\ - [-1,-1,0],\ - [-1,0,-1],\ - [0,-1,-1],\ - [-1,-1,-1]]) - - if self.fcc: #adapt to subgrid, with two times grid spacing - #note: this much simpler than interpolating within tetra/octa tiling (holes of FCC grid) using barycentric coords (PITA) + # look back + ix_iy_iz8_off = np.array([[0, 0, 0], + [-1, 0, 0], + [0, -1, 0], + [0, 0, -1], + [-1, -1, 0], + [-1, 0, -1], + [0, -1, -1], + [-1, -1, -1]]) + + if self.fcc: # adapt to subgrid, with two times grid spacing + # note: this much simpler than interpolating within tetra/octa tiling (holes of FCC grid) using barycentric coords (PITA) ix_iy_iz8_off *= 2 - if np.mod(np.sum(ix_iy_iz),2) == 1: - aa = np.argmin(alpha_xyz) #not unique + if np.mod(np.sum(ix_iy_iz), 2) == 1: + aa = np.argmin(alpha_xyz) # not unique ix_iy_iz[aa] += 1 - for j in [0,1,2]: + for j in [0, 1, 2]: alpha_xyz[j] = (xyzv_list[j][ix_iy_iz[j]] - pos_xyz[j])/(2*h) alpha8 = np.ones((8,)) - xyz8 = np.zeros((8,3)) + xyz8 = np.zeros((8, 3)) for i in range(8): for j in range(3): - xyz8[i,j] = xyzv_list[j][ix_iy_iz[j]+ix_iy_iz8_off[i,j]] - if ix_iy_iz8_off[i,j]==0: + xyz8[i, j] = xyzv_list[j][ix_iy_iz[j]+ix_iy_iz8_off[i, j]] + if ix_iy_iz8_off[i, j] == 0: alpha8[i] *= (1-alpha_xyz[j]) else: alpha8[i] *= alpha_xyz[j] - assert np.allclose(np.sum(alpha8),1) - assert np.allclose(np.sum(alpha8*xyz8.T,-1),pos_xyz) + assert np.allclose(np.sum(alpha8), 1) + assert np.allclose(np.sum(alpha8*xyz8.T, -1), pos_xyz) ix_iy_iz8 = ix_iy_iz + ix_iy_iz8_off - ixyz8 = ix_iy_iz8 @ np.array([Nz*Ny,Nz,1]) + ixyz8 = ix_iy_iz8 @ np.array([Nz*Ny, Nz, 1]) if self.fcc: - assert np.all(np.mod(np.sum(ix_iy_iz8,axis=-1),2)==0) + assert np.all(np.mod(np.sum(ix_iy_iz8, axis=-1), 2) == 0) - return alpha8,ixyz8 + return alpha8, ixyz8 - def check_for_clashes(self,bn_ixyz): - #scheme implementation designed to only have source/receiver in 'regular' air nodes - def _check_for_clashes(_ixyz,bn_ixyz): - ixyz = np.unique(_ixyz) #can have duplicates in receivers - #could speed this up with a numba routine (don't need to know clashes, just say yes or no) - assert np.union1d(ixyz.flat[:],bn_ixyz).size == ixyz.size + bn_ixyz.size + def check_for_clashes(self, bn_ixyz): + # scheme implementation designed to only have source/receiver in 'regular' air nodes + def _check_for_clashes(_ixyz, bn_ixyz): + ixyz = np.unique(_ixyz) # can have duplicates in receivers + # could speed this up with a numba routine (don't need to know clashes, just say yes or no) + assert np.union1d(ixyz.flat[:], bn_ixyz).size == ixyz.size + bn_ixyz.size self.print('intersection with boundaries: passed') timer = TimerDict() timer.tic('check in_xyz') self.print('boundary intersection check with in_ixyz..') - _check_for_clashes(self.in_ixyz,bn_ixyz) + _check_for_clashes(self.in_ixyz, bn_ixyz) self.print(timer.ftoc('check in_xyz')) timer.tic('check in_xyz') self.print('boundary intersection check with out_ixyz..') - _check_for_clashes(self.out_ixyz,bn_ixyz) + _check_for_clashes(self.out_ixyz, bn_ixyz) self.print(timer.ftoc('check in_xyz')) diff --git a/src/python/pffdtd/sim3d/testing.py b/src/python/pffdtd/sim3d/testing.py index f55a716..3c4ed81 100644 --- a/src/python/pffdtd/sim3d/testing.py +++ b/src/python/pffdtd/sim3d/testing.py @@ -11,27 +11,26 @@ def run_engine(sim_dir, engine): - if engine == "python": + if engine == 'python': eng = EnginePython3D(sim_dir) eng.run_all(1) eng.save_outputs() else: - assert engine == "native" + assert engine == 'native' - exe = pathlib.Path(os.environ.get("PFFDTD_ENGINE_3D")).absolute() + exe = pathlib.Path(os.environ.get('PFFDTD_ENGINE_3D')).absolute() assert exe.exists() assert exe.is_file() result = subprocess.run( args=[str(exe), sim_dir], capture_output=True, - text=True, check=True, ) assert result.returncode == 0 def skip_if_native_engine_unavailable(engine): - if engine == "native": - if not os.environ.get("PFFDTD_ENGINE_3D"): - pytest.skip("Native engine not available") + if engine == 'native': + if not os.environ.get('PFFDTD_ENGINE_3D'): + pytest.skip('Native engine not available') diff --git a/src/python/pffdtd/voxelizer/cart_grid.py b/src/python/pffdtd/voxelizer/cart_grid.py index ebae7e0..9954f08 100644 --- a/src/python/pffdtd/voxelizer/cart_grid.py +++ b/src/python/pffdtd/voxelizer/cart_grid.py @@ -63,7 +63,7 @@ def __init__(self, h=None, offset=None, bmin=None, bmax=None, fcc=False): self.print_stats() - def save(self, folder, filename="cart_grid.h5"): + def save(self, folder, filename='cart_grid.h5'): """Save to HDF5 file """ xv = self.xv @@ -78,7 +78,7 @@ def save(self, folder, filename="cart_grid.h5"): else: assert folder.is_dir() - compression = {'compression': "gzip", 'compression_opts': 9} + compression = {'compression': 'gzip', 'compression_opts': 9} file = h5py.File(folder / filename, 'w') file.create_dataset('xv', data=xv, **compression) file.create_dataset('yv', data=yv, **compression) diff --git a/src/python/pffdtd/voxelizer/vox_grid.py b/src/python/pffdtd/voxelizer/vox_grid.py index ed07bb5..8edebc1 100644 --- a/src/python/pffdtd/voxelizer/vox_grid.py +++ b/src/python/pffdtd/voxelizer/vox_grid.py @@ -19,27 +19,30 @@ from pffdtd.geometry.math import iceil from pffdtd.sim3d.room_geometry import RoomGeometry from pffdtd.voxelizer.cart_grid import CartGrid -from pffdtd.voxelizer.vox_grid_base import VoxGridBase,VoxBase +from pffdtd.voxelizer.vox_grid_base import VoxGridBase, VoxBase + class Voxel(VoxBase): - #using cubic voxels for simplicity - def __init__(self,bmin,bmax,ixyz_start,Nhxyz,vox_idx): - super().__init__(bmin,bmax) #this has tri_idxs member - #lower corner (of halo), greater than one + # using cubic voxels for simplicity + def __init__(self, bmin, bmax, ixyz_start, Nhxyz, vox_idx): + super().__init__(bmin, bmax) # this has tri_idxs member + # lower corner (of halo), greater than one self.ixyz_start = ixyz_start - #size of voxel in grid-steps (not points), including halo + # size of voxel in grid-steps (not points), including halo self.Nhxyz = Nhxyz self.idx = vox_idx -#inherits draw_boxes() and and fill() +# inherits draw_boxes() and and fill() + + class VoxGrid(VoxGridBase): - #only for voxelizer, only uses cubic voxels for now - def __init__(self,room_geo,cart_grid,Nvox_est=None,Nh=None): + # only for voxelizer, only uses cubic voxels for now + def __init__(self, room_geo, cart_grid, Nvox_est=None, Nh=None): super().__init__(room_geo) - tris = self.tris - pts = self.pts - Npts = self.Npts + tris = self.tris + pts = self.pts + Npts = self.Npts Ntris = self.Ntris h = cart_grid.h @@ -47,82 +50,82 @@ def __init__(self,room_geo,cart_grid,Nvox_est=None,Nh=None): yv = cart_grid.yv zv = cart_grid.zv Nxyz = cart_grid.Nxyz - Nx,Ny,Nz = Nxyz + Nx, Ny, Nz = Nxyz - assert np.all(npa([xv[0],yv[0],zv[0]]) < np.amin(pts,axis=0)) - assert np.all(npa([xv[Nx-1],yv[Ny-1],zv[Nz-1]]) > np.amax(pts,axis=0)) + assert np.all(npa([xv[0], yv[0], zv[0]]) < np.amin(pts, axis=0)) + assert np.all(npa([xv[Nx-1], yv[Ny-1], zv[Nz-1]]) > np.amax(pts, axis=0)) - #Nh*h is width of non-overlapping part of voxel (with 0.5 spacing around points) - #Nh is also min number of points along one dim - #Nhx*h is size of voxel with halo (one extra layer) - #Nhx is also number of points along one dim (Nxh>=Nh+2) + # Nh*h is width of non-overlapping part of voxel (with 0.5 spacing around points) + # Nh is also min number of points along one dim + # Nhx*h is size of voxel with halo (one extra layer) + # Nhx is also number of points along one dim (Nxh>=Nh+2) - if Nh is None and Nvox_est is None: #heuristic, but seems ok + if Nh is None and Nvox_est is None: # heuristic, but seems ok fac = 0.025 Nvox_est = iceil(fac*np.sqrt(Ntris * np.prod(Nxyz))) - #calculate Nh if estimate given + # calculate Nh if estimate given if Nvox_est is not None: assert Nh is None - if Nvox_est==0: + if Nvox_est == 0: raise - elif Nvox_est==1: - Nh = max((xv.size,yv.size,zv.size))-1 - elif Nvox_est>1: + elif Nvox_est == 1: + Nh = max((xv.size, yv.size, zv.size))-1 + elif Nvox_est > 1: vol = np.prod(room_geo.bmax-room_geo.bmin) vox_side = np.cbrt(vol/Nvox_est) Nh = int(np.round(vox_side/h)) - Nh = max(Nh,4) + Nh = max(Nh, 4) - assert Nh>3 #not good to have too much overlap + assert Nh > 3 # not good to have too much overlap assert np.any(Nxyz >= Nh) self.print(f'Nh={Nh}') - Nvox_xyz = np.int_(np.floor((Nxyz-2)/Nh)) #leaves room for shift to keep one-layer halo + Nvox_xyz = np.int_(np.floor((Nxyz-2)/Nh)) # leaves room for shift to keep one-layer halo self.print(f'Nvox_xyz = {Nvox_xyz}, Nvox = {np.prod(Nvox_xyz)}') - Nvox = int(np.prod(Nvox_xyz)) #keep as int so we can use in ranges + Nvox = int(np.prod(Nvox_xyz)) # keep as int so we can use in ranges vox_idx = 0 self.timer.tic('allocate voxels') - #allocate dummy voxels - self.voxels = [Voxel(npa([0,0,0]),npa([np.inf,np.inf,np.inf]),npa([0,0,0]),npa([0,0,0]),0) for i in range(Nvox)] + # allocate dummy voxels + self.voxels = [Voxel(npa([0, 0, 0]), npa([np.inf, np.inf, np.inf]), npa([0, 0, 0]), npa([0, 0, 0]), 0) for i in range(Nvox)] self.print(self.timer.ftoc('allocate voxels')) self.timer.tic('initialise voxels') - #Nh is step for voxels - Nvox_x,Nvox_y,Nvox_z = Nvox_xyz - #can vectorize most of this but not creating Voxel objects + # Nh is step for voxels + Nvox_x, Nvox_y, Nvox_z = Nvox_xyz + # can vectorize most of this but not creating Voxel objects vix = 0 - Nvx,Nvy,Nvz = Nvox_xyz + Nvx, Nvy, Nvz = Nvox_xyz - pbar = tqdm(total=np.prod(Nvox_xyz),desc=f'vox grid init',ascii=True,leave=False,position=0) + pbar = tqdm(total=np.prod(Nvox_xyz), desc=f'vox grid init', ascii=True, leave=False, position=0) for vix in range(Nvx): ix_start = vix*Nh - if vix=Nh+2) #min size - assert np.all(vox.Nhxyz<2*(Nh+2)) #max size + # start and end values of vox with one-layer still inbounds + assert np.all(vox.Nhxyz >= Nh+2) # min size + assert np.all(vox.Nhxyz < 2*(Nh+2)) # max size vox_idx += 1 pbar.update(1) - #print(f'{vix=},{viy=},{viz=}') + # print(f'{vix=},{viy=},{viz=}') pbar.close() self.print(self.timer.ftoc('initialise voxels')) @@ -147,23 +150,24 @@ def __init__(self,room_geo,cart_grid,Nvox_est=None,Nh=None): self.Nh = Nh self.cg = cart_grid - def print(self,fstring): + def print(self, fstring): print(f'--VOX_GRID: {fstring}') def print_stats(self): super().print_stats() + def main(): import argparse parser = argparse.ArgumentParser() - parser.add_argument('--json', type=str,help='json file to import') - parser.add_argument('--draw', action='store_true',help='draw') - parser.add_argument('--drawpoints', action='store_true',help='draw grid points') - parser.add_argument('--Nvox_est', type=int,help='Nvox roughly') - parser.add_argument('--Nh', type=int,help='Nh') - parser.add_argument('--h', type=float,help='h') - parser.add_argument('--offset', type=float,help='offset') - parser.add_argument('--Nprocs', type=int,help='number of processes') + parser.add_argument('--json', type=str, help='json file to import') + parser.add_argument('--draw', action='store_true', help='draw') + parser.add_argument('--drawpoints', action='store_true', help='draw grid points') + parser.add_argument('--Nvox_est', type=int, help='Nvox roughly') + parser.add_argument('--Nh', type=int, help='Nh') + parser.add_argument('--h', type=float, help='h') + parser.add_argument('--offset', type=float, help='offset') + parser.add_argument('--Nprocs', type=int, help='number of processes') parser.add_argument('--az_el', nargs=2, type=float, help='two angles in deg') parser.set_defaults(draw=False) parser.set_defaults(drawpoints=False) @@ -174,21 +178,21 @@ def main(): parser.set_defaults(offset=3.0) parser.set_defaults(Nh=None) parser.set_defaults(json=None) - parser.set_defaults(az_el=[0.,0.]) + parser.set_defaults(az_el=[0., 0.]) args = parser.parse_args() print(args) - assert args.Nprocs>0 - #assert args.Nvox_est is not None or args.Nh is not None + assert args.Nprocs > 0 + # assert args.Nvox_est is not None or args.Nh is not None assert args.h is not None assert args.json is not None - room_geo = RoomGeometry(args.json,az_el=args.az_el) + room_geo = RoomGeometry(args.json, az_el=args.az_el) room_geo.print_stats() - cart_grid = CartGrid(args.h,args.offset,room_geo.bmin,room_geo.bmax) + cart_grid = CartGrid(args.h, args.offset, room_geo.bmin, room_geo.bmax) cart_grid.print_stats() - vox_grid = VoxGrid(room_geo,cart_grid,args.Nvox_est,args.Nh) + vox_grid = VoxGrid(room_geo, cart_grid, args.Nvox_est, args.Nh) vox_grid.fill(Nprocs=args.Nprocs) vox_grid.print_stats() @@ -201,5 +205,6 @@ def main(): cart_grid.draw_gridpoints() room_geo.show() + if __name__ == '__main__': main() diff --git a/src/python/pffdtd/voxelizer/vox_grid_base.py b/src/python/pffdtd/voxelizer/vox_grid_base.py index 327c514..c592c43 100644 --- a/src/python/pffdtd/voxelizer/vox_grid_base.py +++ b/src/python/pffdtd/voxelizer/vox_grid_base.py @@ -13,20 +13,24 @@ from pffdtd.geometry.box import Box from pffdtd.geometry.tri_box_intersection import tri_box_intersection_vec + class VoxBase: """Base class for a voxel """ - def __init__(self,bmin,bmax): + + def __init__(self, bmin, bmax): self.bmin = bmin self.bmax = bmax - self.tri_idxs = [] #triangle indices as list + self.tri_idxs = [] # triangle indices as list self.tris_pre = None self.tris_mat = None + class VoxGridBase: """Base class for a voxel grid """ - def __init__(self,room_geo): + + def __init__(self, room_geo): tris = room_geo.tris pts = room_geo.pts tris_pre = room_geo.tris_pre @@ -52,8 +56,8 @@ def __init__(self,room_geo): self.timer = TimerDict() self.nprocs = get_default_nprocs() - #fill the grid (primarily using tri-box intersections) - def fill(self,Nprocs=None): + # fill the grid (primarily using tri-box intersections) + def fill(self, Nprocs=None): if Nprocs is None: Nprocs = self.nprocs self.print(f'using {Nprocs} processes') @@ -70,62 +74,56 @@ def fill(self,Nprocs=None): tri_bmin = tris_pre['bmin'] tri_bmax = tris_pre['bmax'] - if Nvox==1: + if Nvox == 1: vox = self.voxels[0] vox.tri_idxs = np.arange(Ntris) vox.tris_pre = self.tris_pre vox.tris_mat = self.mats self.nonempty_idx = [0] else: - if Nprocs>1: + if Nprocs > 1: clear_dat_folder('mmap_dat') - #create shared memory - # Ntris_vox_shm = shared_memory.SharedMemory(create=True,size=Nvox*np.dtype(np.int64).itemsize) - # Ntris_vox = np.frombuffer(Ntris_vox_shm.buf, dtype=np.int64) - Ntris_vox = np.ndarray(Nvox, dtype=np.int64) - #alternative syntax - #Ntris_vox = np.ndarray((Nvox,), dtype=np.int64, buffer=Ntris_vox_shm.buf) - - #use as buffer view to np array - # N_tribox_tests_shm = shared_memory.SharedMemory(create=True,size=Nvox*np.dtype(np.int64).itemsize) - # N_tribox_tests = np.frombuffer(N_tribox_tests_shm.buf, dtype=np.int64) - N_tribox_tests = np.ndarray(Nvox, dtype=np.int64) - + # create shared memory + Ntris_vox_shm = shared_memory.SharedMemory(create=True, size=Nvox*np.dtype(np.int64).itemsize) + Ntris_vox = np.ndarray((Nvox,), dtype=np.int64, buffer=Ntris_vox_shm.buf) Ntris_vox[:] = 0 + + # use as buffer view to np array + N_tribox_tests_shm = shared_memory.SharedMemory(create=True, size=Nvox*np.dtype(np.int64).itemsize) + N_tribox_tests = np.ndarray((Nvox,), dtype=np.int64, buffer=N_tribox_tests_shm.buf) N_tribox_tests[:] = 0 - #looping through boxes makes more sense because we append to voxels (for multithreading) + # looping through boxes makes more sense because we append to voxels (for multithreading) def process_voxel(vox): - candidates = np.nonzero(np.all(np.logical_and(vox.bmax >= tri_bmin,vox.bmin <= tri_bmax),axis=-1))[0] + candidates = np.nonzero(np.all(np.logical_and(vox.bmax >= tri_bmin, vox.bmin <= tri_bmax), axis=-1))[0] tri_idxs_vox = [] N_tribox_tests[vox.idx] += candidates.size - hits = tri_box_intersection_vec(vox.bmin,vox.bmax,tris_pre[candidates]) + hits = tri_box_intersection_vec(vox.bmin, vox.bmax, tris_pre[candidates]) tri_idxs_vox = candidates[hits].tolist() return tri_idxs_vox - def process_voxels(vidx_list,proc_idx): - pbar = tqdm(total=len(vidx_list),desc=f'process {proc_idx:02d} voxgrid processing',ascii=True,leave=False,position=0) + def process_voxels(vidx_list, proc_idx): + pbar = tqdm(total=len(vidx_list), desc=f'process {proc_idx:02d} voxgrid processing', ascii=True, leave=False, position=0) for vox_idx in vidx_list: tri_idxs_vox = process_voxel(self.voxels[vox_idx]) Ntris_vox[vox_idx] = len(tri_idxs_vox) - #if not empty, save vox data as file - if len(tri_idxs_vox)>0: - np.array(tri_idxs_vox,dtype=np.int64).tofile(f'mmap_dat/vox_{vox_idx}.dat') + # if not empty, save vox data as file + if len(tri_idxs_vox) > 0: + np.array(tri_idxs_vox, dtype=np.int64).tofile(f'mmap_dat/vox_{vox_idx}.dat') pbar.update(1) pbar.close() - - if Nprocs==1: #keep separate for debug purposes - #process without intermediate files - pbar = tqdm(total=Nvox,desc=f'single process voxgrid processing',ascii=True,leave=False) + if Nprocs == 1: # keep separate for debug purposes + # process without intermediate files + pbar = tqdm(total=Nvox, desc=f'single process voxgrid processing', ascii=True, leave=False) for vox_idx in range(Nvox): vox = self.voxels[vox_idx] tri_idxs_vox = process_voxel(vox) Ntris_vox[vox_idx] = len(tri_idxs_vox) pbar.update(1) - if Ntris_vox[vox_idx]>0: + if Ntris_vox[vox_idx] > 0: vox.tri_idxs = tri_idxs_vox vox.tris_pre = self.tris_pre[vox.tri_idxs] vox.tris_mat = self.mats[vox.tri_idxs] @@ -133,18 +131,18 @@ def process_voxels(vidx_list,proc_idx): self.nonempty_idx.append(vox_idx) pbar.close() - elif Nprocs>1: + elif Nprocs > 1: procs = [] vox_idx_lists = [[] for i in range(Nprocs)] vox_order = np.random.permutation(Nvox) - #vox_order = np.arange(Nvox) + # vox_order = np.arange(Nvox) for idx in range(Nvox): cc = np.argmin([len(l) for l in vox_idx_lists]) vox_idx_lists[cc].append(vox_order[idx]) for proc_idx in range(Nprocs): - proc = mp.Process(target=process_voxels, args=(vox_idx_lists[proc_idx],proc_idx)) + proc = mp.Process(target=process_voxels, args=(vox_idx_lists[proc_idx], proc_idx)) procs.append(proc) for proc_idx in range(Nprocs): @@ -153,12 +151,12 @@ def process_voxels(vidx_list,proc_idx): for one_proc in procs: one_proc.join() - #now load from temp files + # now load from temp files for vox_idx in range(Nvox): vox = self.voxels[vox_idx] - if Ntris_vox[vox_idx]>0: - #now with one process read data from files - vox.tri_idxs = np.fromfile(f'mmap_dat/vox_{vox_idx}.dat',dtype=np.int64) + if Ntris_vox[vox_idx] > 0: + # now with one process read data from files + vox.tri_idxs = np.fromfile(f'mmap_dat/vox_{vox_idx}.dat', dtype=np.int64) vox.tris_pre = self.tris_pre[vox.tri_idxs] vox.tris_mat = self.mats[vox.tri_idxs] assert Ntris_vox[vox_idx] == len(vox.tri_idxs) @@ -173,48 +171,48 @@ def process_voxels(vidx_list,proc_idx): N_tribox_tests_tot = np.sum(N_tribox_tests) self.print(f'tribox checks={N_tribox_tests_tot} for {Ntris} tris and {Nvox} vox ({N_tribox_tests_tot/(Nvox*Ntris)*100.0:.2f} %)') - #cleanup shared memory - # Ntris_vox_shm.close() - # Ntris_vox_shm.unlink() + # cleanup shared memory + Ntris_vox_shm.close() + Ntris_vox_shm.unlink() - # N_tribox_tests_shm.close() - # N_tribox_tests_shm.unlink() + N_tribox_tests_shm.close() + N_tribox_tests_shm.unlink() self.print(f'tris redundant={Ntris_vox_tot}, {100.*Ntris_vox_tot/self.Ntris:.2f} %') self.print(f'avg tris per voxel={Ntris_vox_tot/Nvox:.2f}') - def print(self,fstring): + def print(self, fstring): print(f'--VOX_GRID_BASE: {fstring}') def print_stats(self): ntris_found = np.sum([len(vox.tri_idxs) for vox in self.voxels]) self.print(f'total tris found in voxels={ntris_found:d}') - #draws non-empty boxes only - def draw_boxes(self,tube_radius,backend='mayavi'): + # draws non-empty boxes only + def draw_boxes(self, tube_radius, backend='mayavi'): Nvox = self.Nvox self.print('drawing boxes..') - boxtris = np.zeros((Nvox*12,3)) - boxpts = np.zeros((Nvox*8,3)) + boxtris = np.zeros((Nvox*12, 3)) + boxpts = np.zeros((Nvox*8, 3)) tp = 0 - #build up a triangular mesh for all boxes in one go + # build up a triangular mesh for all boxes in one go for i in range(len(self.nonempty_idx)): vox = self.voxels[self.nonempty_idx[i]] - assert len(vox.tri_idxs)>0 - box = Box(*(vox.bmax-vox.bmin),shift=vox.bmin,centered=False) - boxtris[i*12:(i+1)*12,:] = box.tris + tp - boxpts[i*8:(i+1)*8,:] = box.verts + assert len(vox.tri_idxs) > 0 + box = Box(*(vox.bmax-vox.bmin), shift=vox.bmin, centered=False) + boxtris[i*12:(i+1)*12, :] = box.tris + tp + boxpts[i*8:(i+1)*8, :] = box.verts tp += 8 self.print(f'{len(self.nonempty_idx)=}') self.print(f'{tp=}') - if backend=='mayavi': + if backend == 'mayavi': from mayavi import mlab - mlab.triangular_mesh(*(boxpts.T),boxtris,representation='mesh',color=(0,1,0),tube_radius=tube_radius) + mlab.triangular_mesh(*(boxpts.T), boxtris, representation='mesh', color=(0, 1, 0), tube_radius=tube_radius) mlab.draw() - elif backend=='polyscope': + elif backend == 'polyscope': import polyscope as ps - pmesh = ps.register_surface_mesh('voxels', boxpts, boxtris,color=(0,1,0),edge_color=(0,1,0),edge_width=tube_radius) - #pmesh.set_transparency(0.0) + pmesh = ps.register_surface_mesh('voxels', boxpts, boxtris, color=(0, 1, 0), edge_color=(0, 1, 0), edge_width=tube_radius) + # pmesh.set_transparency(0.0) self.print('boxes drawn..') diff --git a/src/python/pffdtd/voxelizer/vox_scene.py b/src/python/pffdtd/voxelizer/vox_scene.py index e2d0a21..55f3e59 100644 --- a/src/python/pffdtd/voxelizer/vox_scene.py +++ b/src/python/pffdtd/voxelizer/vox_scene.py @@ -36,7 +36,7 @@ from multiprocessing import shared_memory from tqdm import tqdm -from pffdtd.common.misc import get_default_nprocs, clear_dat_folder,yes_or_no +from pffdtd.common.misc import get_default_nprocs, clear_dat_folder, yes_or_no from pffdtd.common.timerdict import TimerDict from pffdtd.geometry.math import ind2sub3d, dotv from pffdtd.geometry.tri_ray_intersection import tri_ray_intersection_vec @@ -45,29 +45,30 @@ from pffdtd.voxelizer.vox_grid import VoxGrid F_EPS = np.finfo(np.float64).eps -R_EPS = 1e-6 #relative eps (to grid spacing) for near hits -DAT_FOLDER = 'mmap_dat' #change as needed +R_EPS = 1e-6 # relative eps (to grid spacing) for near hits +DAT_FOLDER = 'mmap_dat' # change as needed + class VoxScene: - def __init__(self,room_geo=None,cart_grid=None,vox_grid=None,fcc=False): + def __init__(self, room_geo=None, cart_grid=None, vox_grid=None, fcc=False): self.room_geo = room_geo self.vox_grid = vox_grid self.cart_grid = cart_grid - h = cart_grid.h #grid spacing + h = cart_grid.h # grid spacing - self.NN = 6 #number of nearest neighbours - self.hf = h #scaled h (for FCC) + self.NN = 6 # number of nearest neighbours + self.hf = h # scaled h (for FCC) self.face_area = h*h - self.VV = npa([[1.,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]]) + self.VV = npa([[1., 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]]) self.uvv = self.VV if fcc: self.NN = 12 self.face_area /= np.sqrt(2.0) - self.hf *= np.sqrt(2.0) #actually grid spacing on FCC subgrid + self.hf *= np.sqrt(2.0) # actually grid spacing on FCC subgrid self.VVc = self.VV - self.VV = npa([[+1.,+1,0],[-1,-1,0],[0,+1,+1],[0,-1,-1],[+1,0,+1],[-1,0,-1], \ - [+1,-1,0],[-1,+1,0],[0,+1,-1],[0,-1,+1],[+1,0,-1],[-1,0,+1]]) - self.uvv = self.VV/np.sqrt(2.0) #normalised + self.VV = npa([[+1., +1, 0], [-1, -1, 0], [0, +1, +1], [0, -1, -1], [+1, 0, +1], [-1, 0, -1], + [+1, -1, 0], [-1, +1, 0], [0, +1, -1], [0, -1, +1], [+1, 0, -1], [-1, 0, +1]]) + self.uvv = self.VV/np.sqrt(2.0) # normalised self.print(f'Using FCC subgrid') self.vvh = h * self.VV @@ -76,11 +77,11 @@ def __init__(self,room_geo=None,cart_grid=None,vox_grid=None,fcc=False): self.timer = TimerDict() - def print(self,fstring): + def print(self, fstring): print(f'--VOX_SCENE: {fstring}') - #@memory_profile - def calc_adj(self,Nprocs=None): + # @memory_profile + def calc_adj(self, Nprocs=None): if Nprocs is None: Nprocs = self.nprocs self.print(f'using {Nprocs} processes') @@ -92,200 +93,199 @@ def calc_adj(self,Nprocs=None): h = cg.h hf = self.hf VV = self.VV - vvh = self.vvh #vectors scaled by h (with length gf) - uvv = self.uvv #normalised - ivv = np.int_(VV) #integer grid steps + vvh = self.vvh # vectors scaled by h (with length gf) + uvv = self.uvv # normalised + ivv = np.int_(VV) # integer grid steps face_area = self.face_area - Nx,Ny,Nz = cg.Nxyz + Nx, Ny, Nz = cg.Nxyz Ngridpoints = cg.Npts xv = cg.xv yv = cg.yv zv = cg.zv - xmin,ymin,zmin = cg.xyzmin + xmin, ymin, zmin = cg.xyzmin Nh = vg.Nh Nvox = vg.Nvox - #only process non-empty voxels from vox_grid + # only process non-empty voxels from vox_grid Nvox_nonempty = len(vg.nonempty_idx) self.print(f'Non-empty voxels: {Nvox_nonempty}, {Nvox_nonempty/Nvox*100.0:.2f}%') - min_vox_shape = (Nh,Nh,Nh) #for memory calculation + min_vox_shape = (Nh, Nh, Nh) # for memory calculation - #set up shared memory - # Nb_proc_shm = shared_memory.SharedMemory(create=True,size=Nprocs*np.dtype(np.int64).itemsize) - # Nb_proc = np.frombuffer(Nb_proc_shm.buf,dtype=np.int64) - Nb_proc = np.ndarray(Nprocs,dtype=np.int64) + # set up shared memory + Nb_proc_shm = shared_memory.SharedMemory(create=True, size=Nprocs*np.dtype(np.int64).itemsize) + Nb_proc = np.ndarray((Nprocs,), dtype=np.int64, buffer=Nb_proc_shm.buf) Nb_proc[:] = 0 NN = self.NN - #will need this much for check_adj (less needed for vox data) + # will need this much for check_adj (less needed for vox data) disk_space_needed = Nx*Ny*Nz*(1+self.fcc) self.print(f'{disk_space_needed/2**30=:.3f} GiB') disk_space_available = psutil.disk_usage('.').free self.print(f'{disk_space_available/2**30=:.3f} GiB') - #proceed without asking unless more than 50% of free space + # proceed without asking unless more than 50% of free space if disk_space_needed > 0.5*disk_space_available: - self.print("WARNING: -- disk space usage high") + self.print('WARNING: -- disk space usage high') if not yes_or_no('continue?'): raise Exception('cancelled') clear_dat_folder(DAT_FOLDER) - #this is main function called by mp - def process_voxel(idx,proc_idx): + # this is main function called by mp + def process_voxel(idx, proc_idx): vox_idx = vg.nonempty_idx[idx] vox = vg.voxels[vox_idx] - ##voxel start indices (absolute) and including halos - ix_start,iy_start,iz_start = vox.ixyz_start - #these are widths of voxel, but number points is plus one - Nhx,Nhy,Nhz = vox.Nhxyz + # voxel start indices (absolute) and including halos + ix_start, iy_start, iz_start = vox.ixyz_start + # these are widths of voxel, but number points is plus one + Nhx, Nhy, Nhz = vox.Nhxyz - vox_shape = (Nhx,Nhy,Nhz) #in points + vox_shape = (Nhx, Nhy, Nhz) # in points - #local indices for vox - ix_vox,iy_vox,iz_vox = np.mgrid[0:Nhx,0:Nhy,0:Nhz] + # local indices for vox + ix_vox, iy_vox, iz_vox = np.mgrid[0:Nhx, 0:Nhy, 0:Nhz] - vox_ndist = np.full(vox_shape,np.inf,dtype=np.float64) #distance to nearest hit - vox_bp = np.full(vox_shape,False,dtype=bool) #boundary point? - vox_adj = np.full((*vox_shape,NN),True,dtype=bool) #adjacency to neighbours - vox_nb = np.full(vox_shape,False,dtype=bool) #near a boundary (nothing to do with numba) - vox_tidx = np.full(vox_shape,-1,dtype=np.int32) #tri index for nearest hit + vox_ndist = np.full(vox_shape, np.inf, dtype=np.float64) # distance to nearest hit + vox_bp = np.full(vox_shape, False, dtype=bool) # boundary point? + vox_adj = np.full((*vox_shape, NN), True, dtype=bool) # adjacency to neighbours + vox_nb = np.full(vox_shape, False, dtype=bool) # near a boundary (nothing to do with numba) + vox_tidx = np.full(vox_shape, -1, dtype=np.int32) # tri index for nearest hit - #to store distances to tris - hit_dist = np.full(vox_shape,np.inf) + # to store distances to tris + hit_dist = np.full(vox_shape, np.inf) - xyz_vox = np.c_[xv[ix_start+ix_vox.flat[:]],\ - yv[iy_start+iy_vox.flat[:]],\ + xyz_vox = np.c_[xv[ix_start+ix_vox.flat[:]], + yv[iy_start+iy_vox.flat[:]], zv[iz_start+iz_vox.flat[:]]] - in_mask = np.full(vox_shape,False) - in_mask[1:-1,1:-1,1:-1] = True, + in_mask = np.full(vox_shape, False) + in_mask[1:-1, 1:-1, 1:-1] = True, if self.fcc: - fcc_mask = (np.mod(ix_start+ix_vox+iy_start+iy_vox+iz_start+iz_vox,2)==0) + fcc_mask = (np.mod(ix_start+ix_vox+iy_start+iy_vox+iz_start+iz_vox, 2) == 0) else: - fcc_mask = np.full(vox_shape,True) + fcc_mask = np.full(vox_shape, True) - #loop through triangles in voxel - for tri_pre,tri_ind in zip(vox.tris_pre,vox.tri_idxs): + # loop through triangles in voxel + for tri_pre, tri_ind in zip(vox.tris_pre, vox.tri_idxs): cent = tri_pre['cent'] unor = tri_pre['unor'] tbmin = tri_pre['bmin'] tbmax = tri_pre['bmax'] - #first mask by bounding box - bb_mask = (np.all(xyz_vox >= tbmin - hf*(1+R_EPS),axis=-1) \ - & np.all(xyz_vox <= tbmax + hf*(1+R_EPS),axis=-1)).reshape(vox_shape) + # first mask by bounding box + bb_mask = (np.all(xyz_vox >= tbmin - hf*(1+R_EPS), axis=-1) + & np.all(xyz_vox <= tbmax + hf*(1+R_EPS), axis=-1)).reshape(vox_shape) - #bb_mask &= in_mask.flat[:] #inside halo + # bb_mask &= in_mask.flat[:] #inside halo bb_mask &= fcc_mask if ~np.any(bb_mask): continue - #then mask by distance to plane - dtp = np.full(vox_shape,np.inf,dtype=np.float64) - dtp.flat[bb_mask.flat[:]] = dotv(unor,cent-xyz_vox[bb_mask.flat[:]]) - dist_mask1 = (np.abs(dtp)<= hf*(1+R_EPS)) + # then mask by distance to plane + dtp = np.full(vox_shape, np.inf, dtype=np.float64) + dtp.flat[bb_mask.flat[:]] = dotv(unor, cent-xyz_vox[bb_mask.flat[:]]) + dist_mask1 = (np.abs(dtp) <= hf*(1+R_EPS)) if ~np.any(dist_mask1): continue ray_mask = dist_mask1 - tnb_mask = np.full(vox_shape,False) #this is reset at triangle, accumulates across directions - for k in range(0,NN): + tnb_mask = np.full(vox_shape, False) # this is reset at triangle, accumulates across directions + for k in range(0, NN): ray_o = xyz_vox[ray_mask.flat[:]]-vvh[k] rd = uvv[k] ray_d = rd*np.ones(ray_o.shape) - #returns np.inf or dist>0 if hit - hit_dist = np.full(vox_shape,np.inf) - _,hit_dist.flat[ray_mask.flat[:]] = tri_ray_intersection_vec(ray_o,ray_d,npa([tri_pre]),d_eps=1.0e-3*h) + # returns np.inf or dist>0 if hit + hit_dist = np.full(vox_shape, np.inf) + _, hit_dist.flat[ray_mask.flat[:]] = tri_ray_intersection_vec(ray_o, ray_d, npa([tri_pre]), d_eps=1.0e-3*h) - assert np.all(hit_dist>=0.0) - hit_dist -= hf #shift, doesn't affect np.inf entries - hit_dist[hit_dist<-R_EPS*hf] = np.inf #overwrite hits behind point + assert np.all(hit_dist >= 0.0) + hit_dist -= hf # shift, doesn't affect np.inf entries + hit_dist[hit_dist < -R_EPS*hf] = np.inf # overwrite hits behind point - tnb_mask |= (np.abs(hit_dist)<=R_EPS*hf) - hit_dist[tnb_mask] = np.abs(hit_dist[tnb_mask]) #so ndist is positive + tnb_mask |= (np.abs(hit_dist) <= R_EPS*hf) + hit_dist[tnb_mask] = np.abs(hit_dist[tnb_mask]) # so ndist is positive vox_nb |= tnb_mask - if ~np.any(hit_dist<=hf): + if ~np.any(hit_dist <= hf): continue - hit_dist[hit_dist>(1+R_EPS)*hf] = np.inf #zero those out so they don't interfere + hit_dist[hit_dist > (1+R_EPS)*hf] = np.inf # zero those out so they don't interfere - ii0 = np.flatnonzero(hit_dist<=(1+R_EPS)*hf) #linear indices + ii0 = np.flatnonzero(hit_dist <= (1+R_EPS)*hf) # linear indices - #mark non-adjencies (later use to detect boundary nodes) - vox_adj.reshape(-1,NN)[ii0,k] = False + # mark non-adjencies (later use to detect boundary nodes) + vox_adj.reshape(-1, NN)[ii0, k] = False vox_bp.flat[ii0] = True - #indices where new nearest hit - nh_mask = np.full(vox_shape,False) - nh_mask.flat[ii0] = (hit_dist.flat[ii0] < vox_ndist.flat[ii0]) - vox_ndist[nh_mask] = hit_dist[nh_mask] #update to abs for neg dist + # indices where new nearest hit + nh_mask = np.full(vox_shape, False) + nh_mask.flat[ii0] = (hit_dist.flat[ii0] < vox_ndist.flat[ii0]) + vox_ndist[nh_mask] = hit_dist[nh_mask] # update to abs for neg dist vox_tidx[nh_mask] = tri_ind - #NB can have fictitious boundary faces at edges - #leg on correct side but not overtop triangle... (since intersection not registered) - #..same problem with jordan's theorem tests + # NB can have fictitious boundary faces at edges + # leg on correct side but not overtop triangle... (since intersection not registered) + # ..same problem with jordan's theorem tests - #finally zero out nb points - vox_adj.reshape(-1,NN)[vox_nb.flat[:],:]=False + # finally zero out nb points + vox_adj.reshape(-1, NN)[vox_nb.flat[:], :] = False - assert np.all(~vox_adj.reshape(-1,NN)[tnb_mask.flat[:],:]) + assert np.all(~vox_adj.reshape(-1, NN)[tnb_mask.flat[:], :]) - vox_adj = vox_adj.reshape((-1,NN)) - assert np.all(~vox_adj[vox_nb.flat[:],:]) + vox_adj = vox_adj.reshape((-1, NN)) + assert np.all(~vox_adj[vox_nb.flat[:], :]) - vox_adj[~in_mask.flat[:],:] = True + vox_adj[~in_mask.flat[:], :] = True vox_bp[~in_mask] = False - vox_tidx[~in_mask] = -1 #just so doesn't go into surface-area correction calc + vox_tidx[~in_mask] = -1 # just so doesn't go into surface-area correction calc - #now extract boundary points (redundant) - qq = np.flatnonzero(np.any(~vox_adj,axis=-1)) + # now extract boundary points (redundant) + qq = np.flatnonzero(np.any(~vox_adj, axis=-1)) qq2 = np.flatnonzero(vox_bp.flat[:]) - #print(f'{qq.size=}') + # print(f'{qq.size=}') assert qq.size == qq2.size - #assert np.intersect1d(qq,qq2).size == qq2.size - assert np.all(qq==qq2) - #tally boundary points inside vox without halo (add on to tally for process) + # assert np.intersect1d(qq,qq2).size == qq2.size + assert np.all(qq == qq2) + # tally boundary points inside vox without halo (add on to tally for process) Nb_proc[proc_idx] += np.sum(vox_bp.flat[:]) ndist_bn_vox = vox_ndist.flat[qq] tidx_bn_vox = vox_tidx.flat[qq] - assert np.all(tidx_bn_vox>=-1) #all marked + assert np.all(tidx_bn_vox >= -1) # all marked - adj_bn_vox = vox_adj[qq,:] + adj_bn_vox = vox_adj[qq, :] bn_ixyz_loc_vox = qq - #store vox info on disk (variable size, can't use shared mem), no compression for speed - h5f_vox = h5py.File(Path(DAT_FOLDER) / Path(f'vox_data_{vox.idx}.h5'),'w') + # store vox info on disk (variable size, can't use shared mem), no compression for speed + h5f_vox = h5py.File(Path(DAT_FOLDER) / Path(f'vox_data_{vox.idx}.h5'), 'w') h5f_vox.create_dataset('adj_bn', data=adj_bn_vox) h5f_vox.create_dataset('tidx_bn', data=tidx_bn_vox) h5f_vox.create_dataset('ndist_bn', data=ndist_bn_vox) h5f_vox.create_dataset('bn_ixyz_loc', data=bn_ixyz_loc_vox) h5f_vox.close() - def process_voxels(idx_list,proc_idx): - #using one progress bar because tqdm has problems with multiple, and cleaner - pbar = tqdm(total=len(idx_list),desc=f'process {proc_idx:02d} voxeliser processing',ascii=True,leave=False,position=0) + def process_voxels(idx_list, proc_idx): + # using one progress bar because tqdm has problems with multiple, and cleaner + pbar = tqdm(total=len(idx_list), desc=f'process {proc_idx:02d} voxeliser processing', ascii=True, leave=False, position=0) for idx in idx_list: - process_voxel(idx,proc_idx) + process_voxel(idx, proc_idx) pbar.update(1) pbar.close() self.timer.tic('calc_adj total') self.timer.tic('ray-tri checks') - if Nprocs==1: #no need to use mp - process_voxels(range(Nvox_nonempty),0) + if Nprocs == 1: # no need to use mp + process_voxels(range(Nvox_nonempty), 0) - else: #multiproc with vox grid + else: # multiproc with vox grid procs = [] idx_lists = [[] for i in range(Nprocs)] - #only random shuffle for balancing + # only random shuffle for balancing vox_order = np.random.permutation(Nvox_nonempty) for qq in range(Nvox_nonempty): cc = np.argmin([len(l) for l in idx_lists]) @@ -293,7 +293,7 @@ def process_voxels(idx_list,proc_idx): for proc_idx in range(Nprocs): idx_list = idx_lists[proc_idx] - proc = mp.Process(target=process_voxels, args=(idx_list,proc_idx)) + proc = mp.Process(target=process_voxels, args=(idx_list, proc_idx)) procs.append(proc) for proc_idx in range(Nprocs): @@ -306,36 +306,35 @@ def process_voxels(idx_list,proc_idx): self.timer.tic('consolidate') - #total number of boundary points, to allocate unified arrays + # total number of boundary points, to allocate unified arrays Nbt = np.sum(Nb_proc) self.print(f'{Nbt=}') - #clean up shared memory - # Nb_proc_shm.close() - # Nb_proc_shm.unlink() - #self.print(f'unlink') + # clean up shared memory + Nb_proc_shm.close() + Nb_proc_shm.unlink() - #unified arrays - bn_ixyz = np.full((Nbt,),-1,dtype=np.int64) - adj_bn = np.full((Nbt,NN),True,dtype=bool) - tidx_bn = np.full((Nbt,),-1,dtype=np.int32) - ndist_bn = np.full((Nbt,),np.inf,dtype=np.float64) + # unified arrays + bn_ixyz = np.full((Nbt,), -1, dtype=np.int64) + adj_bn = np.full((Nbt, NN), True, dtype=bool) + tidx_bn = np.full((Nbt,), -1, dtype=np.int32) + ndist_bn = np.full((Nbt,), np.inf, dtype=np.float64) - #consolidate now with single process - pbar = tqdm(total=Nvox_nonempty,desc=f'process 0: consolidate',ascii=True,leave=False,position=0) - bb = 0 #boundary point counter + # consolidate now with single process + pbar = tqdm(total=Nvox_nonempty, desc=f'process 0: consolidate', ascii=True, leave=False, position=0) + bb = 0 # boundary point counter for idx in range(Nvox_nonempty): vox_idx = vg.nonempty_idx[idx] vox = vg.voxels[vox_idx] - Nhx,Nhy,Nhz = vox.Nhxyz - vox_shape = (Nhx,Nhy,Nhz) #in points - ix_start,iy_start,iz_start = vox.ixyz_start + Nhx, Nhy, Nhz = vox.Nhxyz + vox_shape = (Nhx, Nhy, Nhz) # in points + ix_start, iy_start, iz_start = vox.ixyz_start - if len(vox.tri_idxs)==0: + if len(vox.tri_idxs) == 0: continue - #extract boundary points only - h5f_vox = h5py.File(Path(DAT_FOLDER) / Path(f'vox_data_{vox.idx}.h5'),'r') + # extract boundary points only + h5f_vox = h5py.File(Path(DAT_FOLDER) / Path(f'vox_data_{vox.idx}.h5'), 'r') adj_bn_vox = h5f_vox['adj_bn'][...] tidx_bn_vox = h5f_vox['tidx_bn'][...] ndist_bn_vox = h5f_vox['ndist_bn'][...] @@ -344,29 +343,29 @@ def process_voxels(idx_list,proc_idx): qq = bn_ixyz_loc_vox - bn_ix,bn_iy,bn_iz = ind2sub3d(qq,*vox_shape) + bn_ix, bn_iy, bn_iz = ind2sub3d(qq, *vox_shape) bn_ixyz_vox = (bn_iz+iz_start) \ - + (bn_iy+iy_start)*Nz \ - + (bn_ix+ix_start)*Ny*Nz + + (bn_iy+iy_start)*Nz \ + + (bn_ix+ix_start)*Ny*Nz - assert np.all(bn_ixyz_vox=0) + assert np.all(bn_ixyz_vox < Ngridpoints) + assert np.all(bn_ixyz_vox >= 0) Nb_vox = bn_ixyz_vox.size - if Nb_vox>0: - assert bb+Nb_vox<=Nbt + if Nb_vox > 0: + assert bb+Nb_vox <= Nbt adj_bn[bb:bb+Nb_vox] = adj_bn_vox tidx_bn[bb:bb+Nb_vox] = tidx_bn_vox ndist_bn[bb:bb+Nb_vox] = ndist_bn_vox bn_ixyz[bb:bb+Nb_vox] = bn_ixyz_vox bb += Nb_vox pbar.update(1) - assert bb==Nbt + assert bb == Nbt self.print(self.timer.ftoc('consolidate')) pbar.close() - #merge + # merge self.timer.tic('merge') assert np.unique(bn_ixyz).size == bn_ixyz.size @@ -375,61 +374,60 @@ def process_voxels(idx_list,proc_idx): clear_dat_folder(DAT_FOLDER) - #surface area corrections (effective cell surface seen by walls) + # surface area corrections (effective cell surface seen by walls) self.print('materials (+sides)...') self.timer.tic('sides') - bn_ix,bn_iy,bn_iz = ind2sub3d(bn_ixyz,Nx,Ny,Nz) - xyz_bn = np.c_[xv[bn_ix],yv[bn_iy],zv[bn_iz]] - xyz_bn = np.c_[xv[bn_ix],yv[bn_iy],zv[bn_iz]] - dv = dotv(xyz_bn-rg.tris_pre['cent'][tidx_bn],rg.tris_pre['unor'][tidx_bn]) + bn_ix, bn_iy, bn_iz = ind2sub3d(bn_ixyz, Nx, Ny, Nz) + xyz_bn = np.c_[xv[bn_ix], yv[bn_iy], zv[bn_iz]] + xyz_bn = np.c_[xv[bn_ix], yv[bn_iy], zv[bn_iz]] + dv = dotv(xyz_bn-rg.tris_pre['cent'][tidx_bn], rg.tris_pre['unor'][tidx_bn]) - mat_bn = rg.mat_ind[tidx_bn] #default choice - #unmark wrong sides of one-sided triangles - mat_bn[(dv>0) & (rg.mat_side[tidx_bn]==1)] = -1 - mat_bn[(dv<0) & (rg.mat_side[tidx_bn]==2)] = -1 - #anything 'near boundary' mark as rigid - mat_bn[np.all(~adj_bn,axis=-1)] = -1 + mat_bn = rg.mat_ind[tidx_bn] # default choice + # unmark wrong sides of one-sided triangles + mat_bn[(dv > 0) & (rg.mat_side[tidx_bn] == 1)] = -1 + mat_bn[(dv < 0) & (rg.mat_side[tidx_bn] == 2)] = -1 + # anything 'near boundary' mark as rigid + mat_bn[np.all(~adj_bn, axis=-1)] = -1 - self.print(f'Npts = {cg.Npts}, Nbl = {np.sum(mat_bn>-1)}') + self.print(f'Npts = {cg.Npts}, Nbl = {np.sum(mat_bn > -1)}') - if np.any(mat_bn[rg.mat_side[tidx_bn]==0]): + if np.any(mat_bn[rg.mat_side[tidx_bn] == 0]): assert rg.mat_str[-1] == '_RIGID' - assert len(rg.mat_str)==rg.Nmat+1 - assert np.all(mat_bn[rg.mat_side[tidx_bn]==0] == -1) + assert len(rg.mat_str) == rg.Nmat+1 + assert np.all(mat_bn[rg.mat_side[tidx_bn] == 0] == -1) self.print(self.timer.ftoc('sides')) self.print('surface area corrections...') self.timer.tic('surface area corrections') - saf_bn_0 = np.sum(~adj_bn,axis=-1) #this will be number of faces by default - saf_bn = np.zeros(bn_ixyz.size,dtype=np.float64) #this will be a number between 0 and NN - for j in range(0,NN,2): - saf = np.abs(dotv(uvv[j],rg.tris_pre['unor'][tidx_bn])) - saf_bn += (~adj_bn[:,j] + ~adj_bn[:,j+1])*saf + saf_bn_0 = np.sum(~adj_bn, axis=-1) # this will be number of faces by default + saf_bn = np.zeros(bn_ixyz.size, dtype=np.float64) # this will be a number between 0 and NN + for j in range(0, NN, 2): + saf = np.abs(dotv(uvv[j], rg.tris_pre['unor'][tidx_bn])) + saf_bn += (~adj_bn[:, j] + ~adj_bn[:, j+1])*saf - mat_approx_sa = np.zeros((rg.Nmat+1,),dtype=np.float64) - mat_approx_sa_0 = np.zeros((rg.Nmat+1,),dtype=np.float64) + mat_approx_sa = np.zeros((rg.Nmat+1,), dtype=np.float64) + mat_approx_sa_0 = np.zeros((rg.Nmat+1,), dtype=np.float64) - #could parallel reduce (maybe with numba) - np.add.at(mat_approx_sa,mat_bn,face_area*saf_bn) #-1, rigid goes to end - np.add.at(mat_approx_sa_0,mat_bn,face_area*saf_bn_0) #-1, rigid goes to end + # could parallel reduce (maybe with numba) + np.add.at(mat_approx_sa, mat_bn, face_area*saf_bn) # -1, rigid goes to end + np.add.at(mat_approx_sa_0, mat_bn, face_area*saf_bn_0) # -1, rigid goes to end self.print(self.timer.ftoc('surface area corrections')) - #N.B: rg.mat_area takes into account two-sided + # N.B: rg.mat_area takes into account two-sided for i in range(rg.Nmat): self.print(f'mat: {rg.mat_str[i]}, original: {(mat_approx_sa_0[i]/rg.mat_area[i]-1)*100.:.3f}% over, corrected: {(mat_approx_sa[i]/rg.mat_area[i]-1)*100:.3f}% over') - - #attach to class + # attach to class self.bn_ixyz = bn_ixyz - self.adj_bn = adj_bn - self.mat_bn = mat_bn - self.saf_bn = saf_bn + self.adj_bn = adj_bn + self.mat_bn = mat_bn + self.saf_bn = saf_bn self.print(self.timer.ftoc('calc_adj total')) - def save(self,save_folder,compress=None): - #save to HDF5 data file + def save(self, save_folder, compress=None): + # save to HDF5 data file save_folder = Path(save_folder) self.print(f'{save_folder=}') if not save_folder.exists(): @@ -437,15 +435,15 @@ def save(self,save_folder,compress=None): else: assert save_folder.is_dir() - bn_ixyz = self.bn_ixyz #boundary node (bn) linear indices - adj_bn = self.adj_bn #adjacencies for bn - mat_bn = self.mat_bn #material indices for bn - saf_bn = self.saf_bn #boundary-surface area factors (corrections) for bn + bn_ixyz = self.bn_ixyz # boundary node (bn) linear indices + adj_bn = self.adj_bn # adjacencies for bn + mat_bn = self.mat_bn # material indices for bn + saf_bn = self.saf_bn # boundary-surface area factors (corrections) for bn xv = self.cart_grid.xv yv = self.cart_grid.yv zv = self.cart_grid.zv h = self.cart_grid.h - Nx,Ny,Nz = self.cart_grid.Nxyz + Nx, Ny, Nz = self.cart_grid.Nxyz memory_saved = 0 memory_saved += (bn_ixyz.size * bn_ixyz.itemsize) @@ -459,80 +457,80 @@ def save(self,save_folder,compress=None): self.print(f'saving with compression: {compress} ...') if compress is not None: - kw = {'compression': "gzip", 'compression_opts': compress} + kw = {'compression': 'gzip', 'compression_opts': compress} else: kw = {} - h5f = h5py.File(save_folder / Path('vox_out.h5'),'w') + h5f = h5py.File(save_folder / Path('vox_out.h5'), 'w') h5f.create_dataset('bn_ixyz', data=bn_ixyz, **kw) h5f.create_dataset('adj_bn', data=adj_bn, **kw) h5f.create_dataset('mat_bn', data=mat_bn, **kw) h5f.create_dataset('saf_bn', data=saf_bn, **kw) - h5f.create_dataset('xv', data=xv, **kw) #also in cart_grid, but this one can get transformed + h5f.create_dataset('xv', data=xv, **kw) # also in cart_grid, but this one can get transformed h5f.create_dataset('yv', data=yv, **kw) h5f.create_dataset('zv', data=zv, **kw) - h5f.create_dataset('h', data=np.float64(h)) #giving types just to be clear + h5f.create_dataset('h', data=np.float64(h)) # giving types just to be clear h5f.create_dataset('Nx', data=np.int64(Nx)) h5f.create_dataset('Ny', data=np.int64(Ny)) h5f.create_dataset('Nz', data=np.int64(Nz)) h5f.create_dataset('Nb', data=np.int64(bn_ixyz.size)) h5f.close() - #uncomment if importing data to Matlab (Matlab reads HDF5 bool data as strings) - #h5f = h5py.File(save_folder / Path('adj_bn.h5'),'w') - #h5f.create_dataset('adj_bn', data=adj_bn.astype(np.int8), **kw) - #h5f.close() + # uncomment if importing data to Matlab (Matlab reads HDF5 bool data as strings) + # h5f = h5py.File(save_folder / Path('adj_bn.h5'),'w') + # h5f.create_dataset('adj_bn', data=adj_bn.astype(np.int8), **kw) + # h5f.close() def check_adj_full(self): - #check full adjacency map (pre-req for stability) - #uses disk space (but bit compressed) + # check full adjacency map (pre-req for stability) + # uses disk space (but bit compressed) cg = self.cart_grid - Nx,Ny,Nz = cg.Nxyz + Nx, Ny, Nz = cg.Nxyz bn_ixyz = self.bn_ixyz - adj_bn = self.adj_bn - mat_bn = self.mat_bn + adj_bn = self.adj_bn + mat_bn = self.mat_bn Nb = bn_ixyz.size self.print('checking adj...') self.timer.tic('check_full') self.print('mmap...') - #numba has no problem with memmap'd arrays + # numba has no problem with memmap'd arrays if self.fcc: - #FCC uses int16 - adj_full = np.memmap(Path(DAT_FOLDER) / Path('adj_check.dat'), dtype='uint16', mode='w+', shape=(Nx,Ny,Nz)) + # FCC uses int16 + adj_full = np.memmap(Path(DAT_FOLDER) / Path('adj_check.dat'), dtype='uint16', mode='w+', shape=(Nx, Ny, Nz)) self.print('filling full adj map (16-bit compressed)...') adj_full[:] = ~np.uint16(0) - nb_fill_adj_fcc(bn_ixyz,adj_bn,adj_full) + nb_fill_adj_fcc(bn_ixyz, adj_bn, adj_full) self.print('check...') - nb_check_adj_full_fcc(adj_full,Nx,Ny,Nz) + nb_check_adj_full_fcc(adj_full, Nx, Ny, Nz) else: - adj_full = np.memmap(Path(DAT_FOLDER) / Path('adj_check.dat'), dtype='uint8', mode='w+', shape=(Nx,Ny,Nz)) + adj_full = np.memmap(Path(DAT_FOLDER) / Path('adj_check.dat'), dtype='uint8', mode='w+', shape=(Nx, Ny, Nz)) self.print('filling full adj map (8-bit compressed)...') adj_full[:] = ~np.uint8(0) - nb_fill_adj(bn_ixyz,adj_bn,adj_full) + nb_fill_adj(bn_ixyz, adj_bn, adj_full) self.print('check...') - nb_check_adj_full(adj_full,Nx,Ny,Nz) + nb_check_adj_full(adj_full, Nx, Ny, Nz) - del adj_full #release mmap + del adj_full # release mmap clear_dat_folder(DAT_FOLDER) self.print(self.timer.ftoc('check_full')) - def draw(self,backend='mayavi'): - #better to use use polyscope for large grids + def draw(self, backend='mayavi'): + # better to use use polyscope for large grids cg = self.cart_grid rg = self.room_geo bn_ixyz = self.bn_ixyz - adj_bn = self.adj_bn - mat_bn = self.mat_bn - h = cg.h + adj_bn = self.adj_bn + mat_bn = self.mat_bn + h = cg.h xv = cg.xv yv = cg.yv zv = cg.zv - Nx,Ny,Nz = cg.Nxyz + Nx, Ny, Nz = cg.Nxyz Nmat = rg.Nmat colors = rg.colors - bn_ix,bn_iy,bn_iz = ind2sub3d(bn_ixyz,Nx,Ny,Nz) + bn_ix, bn_iy, bn_iz = ind2sub3d(bn_ixyz, Nx, Ny, Nz) if backend == 'mayavi': from mayavi import mlab @@ -540,10 +538,10 @@ def draw(self,backend='mayavi'): import polyscope as ps self.print('drawing...') - for i in range(-1,Nmat): #zero-indexing, -1 is rigid - if i==-1: + for i in range(-1, Nmat): # zero-indexing, -1 is rigid + if i == -1: sf = h/4 - color = (1,1,1) + color = (1, 1, 1) mat = 'rigid' else: sf = h/2 @@ -551,41 +549,42 @@ def draw(self,backend='mayavi'): mat = rg.mat_str[i] self.print(f'drawing mat #{i}: {mat}') - qq = np.flatnonzero((mat_bn==i)) + qq = np.flatnonzero((mat_bn == i)) if backend == 'mayavi': - if i==-1: - mlab.points3d(xv[bn_ix[qq]],yv[bn_iy[qq]],zv[bn_iz[qq]],color=color,\ - mode='cube',scale_factor=sf) + if i == -1: + mlab.points3d(xv[bn_ix[qq]], yv[bn_iy[qq]], zv[bn_iz[qq]], color=color, + mode='cube', scale_factor=sf) else: - mlab.points3d(xv[bn_ix[qq]],yv[bn_iy[qq]],zv[bn_iz[qq]],color=color,\ - mode='sphere',resolution=8,scale_factor=sf) + mlab.points3d(xv[bn_ix[qq]], yv[bn_iy[qq]], zv[bn_iz[qq]], color=color, + mode='sphere', resolution=8, scale_factor=sf) elif backend == 'polyscope': ps_cloud_in = ps.register_point_cloud( mat, - np.c_[xv[bn_ix[qq]],yv[bn_iy[qq]],zv[bn_iz[qq]]], + np.c_[xv[bn_ix[qq]], yv[bn_iy[qq]], zv[bn_iz[qq]]], color=color, - point_render_mode="sphere" + point_render_mode='sphere' ) ps_cloud_in.set_radius(sf/2, relative=False) - vvh = self.vvh - for j in range(0,vvh.shape[0],2): #to draw legs only once - qq = np.flatnonzero(~adj_bn[:,j]) - aa = vvh[j,:] + for j in range(0, vvh.shape[0], 2): # to draw legs only once + qq = np.flatnonzero(~adj_bn[:, j]) + aa = vvh[j, :] x = xv[bn_ix[qq]] y = yv[bn_iy[qq]] z = zv[bn_iz[qq]] if backend == 'mayavi': - uvw = np.tile(aa,(qq.size,1)) - u = uvw[:,0]; v=uvw[:,1]; w=uvw[:,2] - mlab.quiver3d(x,y,z,u,v,w,color=(0,1,0),mode='2ddash',scale_factor=1.) + uvw = np.tile(aa, (qq.size, 1)) + u = uvw[:, 0] + v = uvw[:, 1] + w = uvw[:, 2] + mlab.quiver3d(x, y, z, u, v, w, color=(0, 1, 0), mode='2ddash', scale_factor=1.) elif backend == 'polyscope': - #could condense to one edge network, but keeping simple - nodes = np.r_[np.c_[x,y,z],np.c_[x,y,z]+aa] - edges = np.c_[np.arange(qq.size),np.arange(qq.size)+qq.size] - ps_net = ps.register_curve_network(f'notADJ legs - {j}', nodes, edges, color=(0,1,0)) + # could condense to one edge network, but keeping simple + nodes = np.r_[np.c_[x, y, z], np.c_[x, y, z]+aa] + edges = np.c_[np.arange(qq.size), np.arange(qq.size)+qq.size] + ps_net = ps.register_curve_network(f'notADJ legs - {j}', nodes, edges, color=(0, 1, 0)) ps_net.set_radius(h/40, relative=False) if backend == 'mayavi': @@ -593,89 +592,94 @@ def draw(self,backend='mayavi'): self.print('drawn') -#numba funcs -#these are in fact faster in serial as written and current numba version -#possible to improve with option to choose scheduling types (maybe a feature in future numba versions) -@nb.jit(nopython=True,parallel=False) -def nb_fill_adj(bn_ixyz,adj_bn,adj_full): +# numba funcs +# these are in fact faster in serial as written and current numba version +# possible to improve with option to choose scheduling types (maybe a feature in future numba versions) + + +@nb.jit(nopython=True, parallel=False) +def nb_fill_adj(bn_ixyz, adj_bn, adj_full): for i in nb.prange(bn_ixyz.size): bitmask = np.uint8(0) for jj in np.arange(6): - bitmask |= (adj_bn[i,jj] << jj) + bitmask |= (adj_bn[i, jj] << jj) adj_full.flat[bn_ixyz[i]] = bitmask - #print(f'{bitmask=}, {adj_bn[i]=}') + # print(f'{bitmask=}, {adj_bn[i]=}') -@nb.jit(nopython=True,parallel=False) -def nb_fill_adj_fcc(bn_ixyz,adj_bn,adj_full): + +@nb.jit(nopython=True, parallel=False) +def nb_fill_adj_fcc(bn_ixyz, adj_bn, adj_full): for i in nb.prange(bn_ixyz.size): bitmask = np.uint16(0) for jj in np.arange(12): - bitmask |= (adj_bn[i,jj] << jj) + bitmask |= (adj_bn[i, jj] << jj) adj_full.flat[bn_ixyz[i]] = bitmask - #print(f'{bitmask=}, {adj_bn[i]=}') - -@nb.jit(nopython=True,parallel=False) -def nb_check_adj_full(adj,Nx,Ny,Nz): - assert adj.shape == (Nx,Ny,Nz) - for ix in nb.prange(1,Nx-1): - for iy in np.arange(1,Ny-1): - for iz in np.arange(1,Nz-1): - assert ~(((adj[ix,iy,iz] >> 0) & 1) ^ ((adj[ix+1,iy,iz] >> 1) & 1)) - assert ~(((adj[ix,iy,iz] >> 1) & 1) ^ ((adj[ix-1,iy,iz] >> 0) & 1)) - assert ~(((adj[ix,iy,iz] >> 2) & 1) ^ ((adj[ix,iy+1,iz] >> 3) & 1)) - assert ~(((adj[ix,iy,iz] >> 3) & 1) ^ ((adj[ix,iy-1,iz] >> 2) & 1)) - assert ~(((adj[ix,iy,iz] >> 4) & 1) ^ ((adj[ix,iy,iz+1] >> 5) & 1)) - assert ~(((adj[ix,iy,iz] >> 5) & 1) ^ ((adj[ix,iy,iz-1] >> 4) & 1)) - -@nb.jit(nopython=True,parallel=False) -def nb_check_adj_full_fcc(adj,Nx,Ny,Nz): - assert adj.shape == (Nx,Ny,Nz) - for ix in nb.prange(1,Nx-1): - for iy in np.arange(1,Ny-1): - #for iz in np.arange(1,Nz-1): - #if np.mod(ix+iy+iz,2)==1: - #continue - for iz in np.arange(2-(ix+iy)%2,Nz-1): - assert ~(((adj[ix,iy,iz] >> 0) & 1) ^ ((adj[ix+1,iy+1,iz] >> 1) & 1)) - assert ~(((adj[ix,iy,iz] >> 1) & 1) ^ ((adj[ix-1,iy-1,iz] >> 0) & 1)) - assert ~(((adj[ix,iy,iz] >> 2) & 1) ^ ((adj[ix,iy+1,iz+1] >> 3) & 1)) - assert ~(((adj[ix,iy,iz] >> 3) & 1) ^ ((adj[ix,iy+1,iz+1] >> 2) & 1)) - assert ~(((adj[ix,iy,iz] >> 4) & 1) ^ ((adj[ix+1,iy,iz+1] >> 5) & 1)) - assert ~(((adj[ix,iy,iz] >> 5) & 1) ^ ((adj[ix-1,iy,iz-1] >> 4) & 1)) - assert ~(((adj[ix,iy,iz] >> 6) & 1) ^ ((adj[ix+1,iy-1,iz] >> 7) & 1)) - assert ~(((adj[ix,iy,iz] >> 7) & 1) ^ ((adj[ix-1,iy+1,iz] >> 6) & 1)) - assert ~(((adj[ix,iy,iz] >> 8) & 1) ^ ((adj[ix,iy+1,iz-1] >> 9) & 1)) - assert ~(((adj[ix,iy,iz] >> 9) & 1) ^ ((adj[ix,iy-1,iz+1] >> 8) & 1)) - assert ~(((adj[ix,iy,iz] >> 10) & 1) ^ ((adj[ix+1,iy,iz-1] >> 11) & 1)) - assert ~(((adj[ix,iy,iz] >> 11) & 1) ^ ((adj[ix-1,iy,iz+1] >> 10) & 1)) + # print(f'{bitmask=}, {adj_bn[i]=}') + + +@nb.jit(nopython=True, parallel=False) +def nb_check_adj_full(adj, Nx, Ny, Nz): + assert adj.shape == (Nx, Ny, Nz) + for ix in nb.prange(1, Nx-1): + for iy in np.arange(1, Ny-1): + for iz in np.arange(1, Nz-1): + assert ~(((adj[ix, iy, iz] >> 0) & 1) ^ ((adj[ix+1, iy, iz] >> 1) & 1)) + assert ~(((adj[ix, iy, iz] >> 1) & 1) ^ ((adj[ix-1, iy, iz] >> 0) & 1)) + assert ~(((adj[ix, iy, iz] >> 2) & 1) ^ ((adj[ix, iy+1, iz] >> 3) & 1)) + assert ~(((adj[ix, iy, iz] >> 3) & 1) ^ ((adj[ix, iy-1, iz] >> 2) & 1)) + assert ~(((adj[ix, iy, iz] >> 4) & 1) ^ ((adj[ix, iy, iz+1] >> 5) & 1)) + assert ~(((adj[ix, iy, iz] >> 5) & 1) ^ ((adj[ix, iy, iz-1] >> 4) & 1)) + + +@nb.jit(nopython=True, parallel=False) +def nb_check_adj_full_fcc(adj, Nx, Ny, Nz): + assert adj.shape == (Nx, Ny, Nz) + for ix in nb.prange(1, Nx-1): + for iy in np.arange(1, Ny-1): + # for iz in np.arange(1,Nz-1): + # if np.mod(ix+iy+iz,2)==1: + # continue + for iz in np.arange(2-(ix+iy) % 2, Nz-1): + assert ~(((adj[ix, iy, iz] >> 0) & 1) ^ ((adj[ix+1, iy+1, iz] >> 1) & 1)) + assert ~(((adj[ix, iy, iz] >> 1) & 1) ^ ((adj[ix-1, iy-1, iz] >> 0) & 1)) + assert ~(((adj[ix, iy, iz] >> 2) & 1) ^ ((adj[ix, iy+1, iz+1] >> 3) & 1)) + assert ~(((adj[ix, iy, iz] >> 3) & 1) ^ ((adj[ix, iy+1, iz+1] >> 2) & 1)) + assert ~(((adj[ix, iy, iz] >> 4) & 1) ^ ((adj[ix+1, iy, iz+1] >> 5) & 1)) + assert ~(((adj[ix, iy, iz] >> 5) & 1) ^ ((adj[ix-1, iy, iz-1] >> 4) & 1)) + assert ~(((adj[ix, iy, iz] >> 6) & 1) ^ ((adj[ix+1, iy-1, iz] >> 7) & 1)) + assert ~(((adj[ix, iy, iz] >> 7) & 1) ^ ((adj[ix-1, iy+1, iz] >> 6) & 1)) + assert ~(((adj[ix, iy, iz] >> 8) & 1) ^ ((adj[ix, iy+1, iz-1] >> 9) & 1)) + assert ~(((adj[ix, iy, iz] >> 9) & 1) ^ ((adj[ix, iy-1, iz+1] >> 8) & 1)) + assert ~(((adj[ix, iy, iz] >> 10) & 1) ^ ((adj[ix+1, iy, iz-1] >> 11) & 1)) + assert ~(((adj[ix, iy, iz] >> 11) & 1) ^ ((adj[ix-1, iy, iz+1] >> 10) & 1)) def main(): import argparse parser = argparse.ArgumentParser() - parser.add_argument('--json', type=str,help='json file to import') - parser.add_argument('--Nvox_est', type=int,help='Nvox roughly') - parser.add_argument('--draw', action='store_true',help='draw') - parser.add_argument('--Nh', type=int,help='Nh') - parser.add_argument('--h', type=float,help='h') - parser.add_argument('--fcc', action='store_true',help='fcc grid') - parser.add_argument('--offset', type=float,help='offset') - parser.add_argument('--Nprocs', type=int,help='number of processes') - #parser.add_argument('--draw_backend', type=str,help='mayavi or polyscope') - parser.add_argument('--area_eps', type=float,help='for pruning degenerate triangels') - parser.add_argument('--check_full', action='store_true',help='check whole adj') - parser.add_argument('--save_folder', type=str,help='where to save') + parser.add_argument('--json', type=str, help='json file to import') + parser.add_argument('--Nvox_est', type=int, help='Nvox roughly') + parser.add_argument('--draw', action='store_true', help='draw') + parser.add_argument('--Nh', type=int, help='Nh') + parser.add_argument('--h', type=float, help='h') + parser.add_argument('--fcc', action='store_true', help='fcc grid') + parser.add_argument('--offset', type=float, help='offset') + parser.add_argument('--Nprocs', type=int, help='number of processes') + # parser.add_argument('--draw_backend', type=str,help='mayavi or polyscope') + parser.add_argument('--area_eps', type=float, help='for pruning degenerate triangels') + parser.add_argument('--check_full', action='store_true', help='check whole adj') + parser.add_argument('--save_folder', type=str, help='where to save') parser.add_argument('--az_el', nargs=2, type=float, help='two angles in deg') - parser.add_argument('--polyscope', action='store_true',help='use polyscope backend') + parser.add_argument('--polyscope', action='store_true', help='use polyscope backend') parser.set_defaults(draw=False) parser.set_defaults(fcc=False) - #parser.set_defaults(draw_backend='mayavi') + # parser.set_defaults(draw_backend='mayavi') parser.set_defaults(polyscope=False) parser.set_defaults(Nvox_est=None) parser.set_defaults(Nprocs=get_default_nprocs()) parser.set_defaults(offset=3.0) parser.set_defaults(area_eps=1.0e-10) - parser.set_defaults(az_el=[0.,0.]) + parser.set_defaults(az_el=[0., 0.]) parser.set_defaults(h=None) parser.set_defaults(Nh=None) parser.set_defaults(json=None) @@ -683,28 +687,28 @@ def main(): parser.set_defaults(save_folder=None) args = parser.parse_args() print(args) - assert args.Nprocs>0 - #assert args.Nvox_est is not None or args.Nh is not None + assert args.Nprocs > 0 + # assert args.Nvox_est is not None or args.Nh is not None assert args.h is not None assert args.json is not None - assert args.offset>2.0 + assert args.offset > 2.0 if args.polyscope: - draw_backend='polyscope' + draw_backend = 'polyscope' else: - draw_backend='mayavi' + draw_backend = 'mayavi' - room_geo = RoomGeometry(args.json,az_el=args.az_el,area_eps=args.area_eps) + room_geo = RoomGeometry(args.json, az_el=args.az_el, area_eps=args.area_eps) room_geo.print_stats() - cart_grid = CartGrid(args.h,args.offset,room_geo.bmin,room_geo.bmax) + cart_grid = CartGrid(args.h, args.offset, room_geo.bmin, room_geo.bmax) cart_grid.print_stats() - vox_grid = VoxGrid(room_geo,cart_grid,args.Nvox_est,args.Nh) + vox_grid = VoxGrid(room_geo, cart_grid, args.Nvox_est, args.Nh) vox_grid.fill(Nprocs=args.Nprocs) vox_grid.print_stats() - vox_scene = VoxScene(room_geo,cart_grid,vox_grid,fcc=args.fcc) + vox_scene = VoxScene(room_geo, cart_grid, vox_grid, fcc=args.fcc) vox_scene.calc_adj(Nprocs=args.Nprocs) if args.check_full: @@ -714,9 +718,10 @@ def main(): vox_scene.save(args.save_folder) if args.draw: - room_geo.draw(wireframe=False,backend=draw_backend) + room_geo.draw(wireframe=False, backend=draw_backend) vox_scene.draw(backend=draw_backend) room_geo.show(backend=draw_backend) + if __name__ == '__main__': main() diff --git a/src/python/test/test_sim2d_modes.py b/src/python/test/test_sim2d_modes.py index 547c0e6..477784d 100644 --- a/src/python/test/test_sim2d_modes.py +++ b/src/python/test/test_sim2d_modes.py @@ -25,8 +25,8 @@ def model(*, Lx=None, Ly=None, Nx=None, Ny=None, dx=None, X=None, Y=None, in_mas def test_sim2d_modes(tmp_path): - if not os.environ.get("PFFDTD_ENGINE_2D"): - pytest.skip("Native 2D engine not available") + if not os.environ.get('PFFDTD_ENGINE_2D'): + pytest.skip('Native 2D engine not available') sim_setup_2d( sim_dir=tmp_path, @@ -45,16 +45,16 @@ def test_sim2d_modes(tmp_path): ) runner = CliRunner() - args = ["sim2d", "run", "--sim_dir", str(tmp_path), "--out", "out-py.h5"] + args = ['sim2d', 'run', '--sim_dir', str(tmp_path), '--out', 'out-py.h5'] result = runner.invoke(cli, args) assert result.exit_code == 0 - exe = pathlib.Path(os.environ.get("PFFDTD_ENGINE_2D")).absolute() + exe = pathlib.Path(os.environ.get('PFFDTD_ENGINE_2D')).absolute() assert exe.exists() assert exe.is_file() result = subprocess.run( - args=[str(exe), "-s", str(tmp_path), "-o", "out-cpp.h5"], + args=[str(exe), '-s', str(tmp_path), '-o', 'out-cpp.h5'], capture_output=True, text=True, check=True, diff --git a/src/python/test/test_sim3d_detect_room_modes.py b/src/python/test/test_sim3d_detect_room_modes.py index 9c0b061..a99f865 100644 --- a/src/python/test/test_sim3d_detect_room_modes.py +++ b/src/python/test/test_sim3d_detect_room_modes.py @@ -12,12 +12,12 @@ from pffdtd.sim3d.process_outputs import process_outputs -@pytest.mark.parametrize("engine", ["python", "native"]) +@pytest.mark.parametrize('engine', ['python', 'native']) @pytest.mark.parametrize( - "room,fmax,ppw,fcc,dx_scale,tolerance_pct", + 'room,fmax,ppw,fcc,dx_scale,tolerance_pct', [ ((2.8, 2.076, 1.48), 400, 10.5, False, 2, 1.7), - ((3.0, 1.0, 2.0), 600, 7.7, True, 3, 3.4), + ((3.0, 1.0, 2.0), 600, 7.7, True, 3, 3.8), ] ) def test_sim3d_detect_room_modes(tmp_path, engine, room, fmax, ppw, fcc, dx_scale, tolerance_pct): @@ -36,8 +36,8 @@ def test_sim3d_detect_room_modes(tmp_path, engine, room, fmax, ppw, fcc, dx_scal offset = dx*dx_scale room = RoomModelBuilder(L, W, H) - room.add_source("S1", [offset, offset, offset]) - room.add_receiver("R1", [W-offset, L-offset, H-offset]) + room.add_source('S1', [offset, offset, offset]) + room.add_receiver('R1', [W-offset, L-offset, H-offset]) room.build(model_file) write_freq_ind_mat_from_Yn(convert_Sabs_to_Yn(0.03), root_dir / material) @@ -57,6 +57,7 @@ def test_sim3d_detect_room_modes(tmp_path, engine, room, fmax, ppw, fcc, dx_scal PPW=ppw, insig_type='impulse', save_folder=sim_dir, + Nprocs=1, ) run_engine(sim_dir=sim_dir, engine=engine) @@ -69,7 +70,7 @@ def test_sim3d_detect_room_modes(tmp_path, engine, room, fmax, ppw, fcc, dx_scal fcut_lowpass=fmax, order_lowpass=8, symmetric_lowpass=True, - air_abs_filter="none", + air_abs_filter='none', save_wav=True, plot_raw=False, plot=False, diff --git a/src/python/test/test_sim3d_infinite_baffle.py b/src/python/test/test_sim3d_infinite_baffle.py index 84d2ec7..29d702f 100644 --- a/src/python/test/test_sim3d_infinite_baffle.py +++ b/src/python/test/test_sim3d_infinite_baffle.py @@ -14,7 +14,7 @@ from pffdtd.sim3d.process_outputs import process_outputs -@pytest.mark.parametrize("engine", ["python", "native"]) +@pytest.mark.parametrize('engine', ['python', 'native']) def test_sim3d_infinite_baffle(tmp_path, engine): skip_if_native_engine_unavailable(engine) @@ -41,15 +41,15 @@ def test_sim3d_infinite_baffle(tmp_path, engine): r3 = [r3[0], r3[1], s1[2]] model = { - "mats_hash": { - "Baffle": { - "tris": [ + 'mats_hash': { + 'Baffle': { + 'tris': [ [0, 2, 1], [0, 3, 2], [1, 5, 4], [1, 3, 5] ], - "pts": [ + 'pts': [ [0.0, depth, 0.0], [baffle_size/2, depth, 0.0], [baffle_size/2, depth, baffle_size], @@ -57,19 +57,19 @@ def test_sim3d_infinite_baffle(tmp_path, engine): [baffle_size, depth, 0.0], [baffle_size, depth, baffle_size] ], - "color": [255, 255, 255], - "sides": [1, 1, 1, 1] + 'color': [255, 255, 255], + 'sides': [1, 1, 1, 1] } }, - "sources": [{"name": "S1", "xyz": s1}], - "receivers": [ - {"name": "R1", "xyz": r1}, - {"name": "R2", "xyz": r2}, - {"name": "R3", "xyz": r3}, + 'sources': [{'name': 'S1', 'xyz': s1}], + 'receivers': [ + {'name': 'R1', 'xyz': r1}, + {'name': 'R2', 'xyz': r2}, + {'name': 'R3', 'xyz': r3}, ] } - with open(model_file, "w") as file: + with open(model_file, 'w') as file: json.dump(model, file) write_freq_ind_mat_from_Yn(convert_Sabs_to_Yn(0.01), root_dir / material) @@ -87,6 +87,7 @@ def test_sim3d_infinite_baffle(tmp_path, engine): save_folder=sim_dir, bmax=[baffle_size, depth, baffle_size], bmin=[0, 0, 0], + Nprocs=1, ) run_engine(sim_dir=sim_dir, engine=engine) @@ -99,15 +100,15 @@ def test_sim3d_infinite_baffle(tmp_path, engine): fcut_lowpass=fmax, order_lowpass=8, symmetric_lowpass=True, - air_abs_filter="none", + air_abs_filter='none', save_wav=True, plot_raw=False, plot=False, ) - fs_1, buf_1 = wavread(sim_dir / "R001_out_normalised.wav") - fs_2, buf_2 = wavread(sim_dir / "R002_out_normalised.wav") - fs_3, buf_3 = wavread(sim_dir / "R003_out_normalised.wav") + fs_1, buf_1 = wavread(sim_dir / 'R001_out_normalised.wav') + fs_2, buf_2 = wavread(sim_dir / 'R002_out_normalised.wav') + fs_3, buf_3 = wavread(sim_dir / 'R003_out_normalised.wav') assert fs_1 == fs_2 assert fs_1 == fs_3 diff --git a/src/python/test/test_sim3d_locate_sound_source.py b/src/python/test/test_sim3d_locate_sound_source.py index 23e5ff7..a90c076 100644 --- a/src/python/test/test_sim3d_locate_sound_source.py +++ b/src/python/test/test_sim3d_locate_sound_source.py @@ -15,7 +15,7 @@ from pffdtd.sim3d.process_outputs import process_outputs -@pytest.mark.parametrize("engine", ["python", "native"]) +@pytest.mark.parametrize('engine', ['python', 'native']) def test_sim3d_locate_sound_source(tmp_path, engine): skip_if_native_engine_unavailable(engine) @@ -41,16 +41,16 @@ def test_sim3d_locate_sound_source(tmp_path, engine): builder = RoomModelBuilder(length, width, height) builder.with_colors({ - "Ceiling": [200, 200, 200], - "Floor": [151, 134, 122], - "Walls": [255, 255, 255], + 'Ceiling': [200, 200, 200], + 'Floor': [151, 134, 122], + 'Walls': [255, 255, 255], }) - builder.add_source("S1", source) - builder.add_receiver("R1", list(mics[1-1]/2+[0.5, 0.5, 0.5])) - builder.add_receiver("R2", list(mics[2-1]/2+[0.5, 0.5, 0.5])) - builder.add_receiver("R3", list(mics[3-1]/2+[0.5, 0.5, 0.5])) - builder.add_receiver("R4", list(mics[4-1]/2+[0.5, 0.5, 0.5])) + builder.add_source('S1', source) + builder.add_receiver('R1', list(mics[1-1]/2+[0.5, 0.5, 0.5])) + builder.add_receiver('R2', list(mics[2-1]/2+[0.5, 0.5, 0.5])) + builder.add_receiver('R3', list(mics[3-1]/2+[0.5, 0.5, 0.5])) + builder.add_receiver('R4', list(mics[4-1]/2+[0.5, 0.5, 0.5])) builder.build(model_file) write_freq_ind_mat_from_Yn(convert_Sabs_to_Yn(0.9512), root_dir / material) @@ -70,6 +70,7 @@ def test_sim3d_locate_sound_source(tmp_path, engine): PPW=ppw, insig_type='impulse', save_folder=sim_dir, + Nprocs=1, ) run_engine(sim_dir=sim_dir, engine=engine) @@ -82,16 +83,16 @@ def test_sim3d_locate_sound_source(tmp_path, engine): fcut_lowpass=fmax, order_lowpass=8, symmetric_lowpass=True, - air_abs_filter="none", + air_abs_filter='none', save_wav=True, plot_raw=False, plot=False, ) - fs1, mic1 = wavread(sim_dir/"R001_out_normalised.wav") - fs2, mic2 = wavread(sim_dir/"R002_out_normalised.wav") - fs3, mic3 = wavread(sim_dir/"R003_out_normalised.wav") - fs4, mic4 = wavread(sim_dir/"R004_out_normalised.wav") + fs1, mic1 = wavread(sim_dir/'R001_out_normalised.wav') + fs2, mic2 = wavread(sim_dir/'R002_out_normalised.wav') + fs3, mic3 = wavread(sim_dir/'R003_out_normalised.wav') + fs4, mic4 = wavread(sim_dir/'R004_out_normalised.wav') assert fs1 == fs2 assert fs1 == fs3 assert fs1 == fs4 @@ -99,15 +100,15 @@ def test_sim3d_locate_sound_source(tmp_path, engine): fs = fs1 mic_sigs = [mic1, mic2, mic3, mic4] - with open(model_file, "r") as f: + with open(model_file, 'r') as f: model = json.load(f) mic_pos = np.array([ - model["receivers"][0]["xyz"], - model["receivers"][1]["xyz"], - model["receivers"][2]["xyz"], - model["receivers"][3]["xyz"], + model['receivers'][0]['xyz'], + model['receivers'][1]['xyz'], + model['receivers'][2]['xyz'], + model['receivers'][3]['xyz'], ]) - actual = model["sources"][0]["xyz"] + actual = model['sources'][0]['xyz'] estimated = locate_sound_source(mic_pos, mic_sigs, fs, verbose=True) assert np.linalg.norm(actual-estimated) <= 0.1